diff options
author | Mauro Carvalho Chehab <mchehab@kernel.org> | 2022-03-10 16:40:21 +0100 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@kernel.org> | 2022-03-14 09:42:59 +0100 |
commit | 46fb99951fe2c71adfd7f4ea4439af5ed5ebb7f7 (patch) | |
tree | 3316c14ab74d11b910331a03a2851f443b0015a9 /drivers/media/platform/nxp | |
parent | 69c5ee8aae03bb9f9a8a825175a1cd0644eaf20c (diff) |
media: platform: place NXP drivers on a separate dir
In order to cleanup the main platform media directory, move NXP
drivers to their own directory.
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Diffstat (limited to 'drivers/media/platform/nxp')
-rw-r--r-- | drivers/media/platform/nxp/Kconfig | 66 | ||||
-rw-r--r-- | drivers/media/platform/nxp/Makefile | 8 | ||||
-rw-r--r-- | drivers/media/platform/nxp/fsl-viu.c | 1599 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/Kconfig | 12 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/Makefile | 3 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c | 168 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h | 140 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c | 2254 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h | 183 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-mipi-csis.c | 1581 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-pxp.c | 1788 | ||||
-rw-r--r-- | drivers/media/platform/nxp/imx-pxp.h | 1685 | ||||
-rw-r--r-- | drivers/media/platform/nxp/mx2_emmaprp.c | 912 |
13 files changed, 10399 insertions, 0 deletions
diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig new file mode 100644 index 000000000000..df52d32ebe50 --- /dev/null +++ b/drivers/media/platform/nxp/Kconfig @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# V4L drivers + +menuconfig VIDEO_IMX + bool "V4L2 capture drivers for NXP i.MX devices" + depends on V4L_PLATFORM_DRIVERS + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV && VIDEO_V4L2 + help + Say yes here to enable support for capture drivers on i.MX SoCs. + Support for the single SoC features are selectable in the sub-menu + options. + +if VIDEO_IMX + +config VIDEO_IMX_MIPI_CSIS + tristate "MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + default n + help + Video4Linux2 sub-device driver for the MIPI CSI-2 CSIS receiver + v3.3/v3.6.3 found on some i.MX7 and i.MX8 SoCs. + +endif # VIDEO_IMX + +config VIDEO_VIU + tristate "Freescale/NXP VIU Video Driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_V4L2 && (PPC_MPC512x || COMPILE_TEST) && I2C + select VIDEOBUF_DMA_CONTIG + default y + help + Support for Freescale VIU video driver. This device captures + video data, or overlays video on DIU frame buffer. + + Say Y here if you want to enable VIU device on MPC5121e Rev2+. + In doubt, say N. + +# mem2mem drivers + +config VIDEO_IMX_PXP + tristate "i.MX Pixel Pipeline (PXP)" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && VIDEO_V4L2 && (ARCH_MXC || COMPILE_TEST) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + The i.MX Pixel Pipeline is a memory-to-memory engine for scaling, + color space conversion, and rotation. + +config VIDEO_MX2_EMMAPRP + tristate "Freescale/NXP MX2 eMMa-PrP support" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && VIDEO_V4L2 + depends on SOC_IMX27 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + MX2X chips have a PrP that can be used to process buffers from + memory to memory. Operations include resizing and format + conversion. + +source "drivers/media/platform/nxp/imx-jpeg/Kconfig" diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile new file mode 100644 index 000000000000..efc38c6578ce --- /dev/null +++ b/drivers/media/platform/nxp/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += imx-jpeg/ + +obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o +obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o +obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o +obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o diff --git a/drivers/media/platform/nxp/fsl-viu.c b/drivers/media/platform/nxp/fsl-viu.c new file mode 100644 index 000000000000..afc96f6db2a1 --- /dev/null +++ b/drivers/media/platform/nxp/fsl-viu.c @@ -0,0 +1,1599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Freescale VIU video driver + * + * Authors: Hongjun Chen <hong-jun.chen@freescale.com> + * Porting to 2.6.35 by DENX Software Engineering, + * Anatolij Gustschin <agust@denx.de> + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/slab.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-event.h> +#include <media/videobuf-dma-contig.h> + +#define DRV_NAME "fsl_viu" +#define VIU_VERSION "0.5.1" + +#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ + +#define VIU_VID_MEM_LIMIT 4 /* Video memory limit, in Mb */ + +/* I2C address of video decoder chip is 0x4A */ +#define VIU_VIDEO_DECODER_ADDR 0x25 + +static int info_level; + +#define dprintk(level, fmt, arg...) \ + do { \ + if (level <= info_level) \ + printk(KERN_DEBUG "viu: " fmt , ## arg); \ + } while (0) + +/* + * Basic structures + */ +struct viu_fmt { + u32 fourcc; /* v4l2 format id */ + u32 pixelformat; + int depth; +}; + +static struct viu_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .pixelformat = V4L2_PIX_FMT_RGB565, + .depth = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .pixelformat = V4L2_PIX_FMT_RGB32, + .depth = 32, + } +}; + +struct viu_dev; +struct viu_buf; + +/* buffer for one video frame */ +struct viu_buf { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + struct viu_fmt *fmt; +}; + +struct viu_dmaqueue { + struct viu_dev *dev; + struct list_head active; + struct list_head queued; + struct timer_list timeout; +}; + +struct viu_status { + u32 field_irq; + u32 vsync_irq; + u32 hsync_irq; + u32 vstart_irq; + u32 dma_end_irq; + u32 error_irq; +}; + +struct viu_reg { + u32 status_cfg; + u32 luminance; + u32 chroma_r; + u32 chroma_g; + u32 chroma_b; + u32 field_base_addr; + u32 dma_inc; + u32 picture_count; + u32 req_alarm; + u32 alpha; +} __attribute__ ((packed)); + +struct viu_dev { + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler hdl; + struct mutex lock; + spinlock_t slock; + int users; + + struct device *dev; + /* various device info */ + struct video_device *vdev; + struct viu_dmaqueue vidq; + enum v4l2_field capfield; + int field; + int first; + int dma_done; + + /* Hardware register area */ + struct viu_reg __iomem *vr; + + /* Interrupt vector */ + int irq; + struct viu_status irqs; + + /* video overlay */ + struct v4l2_framebuffer ovbuf; + struct viu_fmt *ovfmt; + unsigned int ovenable; + enum v4l2_field ovfield; + + /* crop */ + struct v4l2_rect crop_current; + + /* clock pointer */ + struct clk *clk; + + /* decoder */ + struct v4l2_subdev *decoder; + + v4l2_std_id std; +}; + +struct viu_fh { + /* must remain the first field of this struct */ + struct v4l2_fh fh; + struct viu_dev *dev; + + /* video capture */ + struct videobuf_queue vb_vidq; + spinlock_t vbq_lock; /* spinlock for the videobuf queue */ + + /* video overlay */ + struct v4l2_window win; + struct v4l2_clip clips[1]; + + /* video capture */ + struct viu_fmt *fmt; + int width, height, sizeimage; + enum v4l2_buf_type type; +}; + +static struct viu_reg reg_val; + +/* + * Macro definitions of VIU registers + */ + +/* STATUS_CONFIG register */ +enum status_config { + SOFT_RST = 1 << 0, + + ERR_MASK = 0x0f << 4, /* Error code mask */ + ERR_NO = 0x00, /* No error */ + ERR_DMA_V = 0x01 << 4, /* DMA in vertical active */ + ERR_DMA_VB = 0x02 << 4, /* DMA in vertical blanking */ + ERR_LINE_TOO_LONG = 0x04 << 4, /* Line too long */ + ERR_TOO_MANG_LINES = 0x05 << 4, /* Too many lines in field */ + ERR_LINE_TOO_SHORT = 0x06 << 4, /* Line too short */ + ERR_NOT_ENOUGH_LINE = 0x07 << 4, /* Not enough lines in field */ + ERR_FIFO_OVERFLOW = 0x08 << 4, /* FIFO overflow */ + ERR_FIFO_UNDERFLOW = 0x09 << 4, /* FIFO underflow */ + ERR_1bit_ECC = 0x0a << 4, /* One bit ECC error */ + ERR_MORE_ECC = 0x0b << 4, /* Two/more bits ECC error */ + + INT_FIELD_EN = 0x01 << 8, /* Enable field interrupt */ + INT_VSYNC_EN = 0x01 << 9, /* Enable vsync interrupt */ + INT_HSYNC_EN = 0x01 << 10, /* Enable hsync interrupt */ + INT_VSTART_EN = 0x01 << 11, /* Enable vstart interrupt */ + INT_DMA_END_EN = 0x01 << 12, /* Enable DMA end interrupt */ + INT_ERROR_EN = 0x01 << 13, /* Enable error interrupt */ + INT_ECC_EN = 0x01 << 14, /* Enable ECC interrupt */ + + INT_FIELD_STATUS = 0x01 << 16, /* field interrupt status */ + INT_VSYNC_STATUS = 0x01 << 17, /* vsync interrupt status */ + INT_HSYNC_STATUS = 0x01 << 18, /* hsync interrupt status */ + INT_VSTART_STATUS = 0x01 << 19, /* vstart interrupt status */ + INT_DMA_END_STATUS = 0x01 << 20, /* DMA end interrupt status */ + INT_ERROR_STATUS = 0x01 << 21, /* error interrupt status */ + + DMA_ACT = 0x01 << 27, /* Enable DMA transfer */ + FIELD_NO = 0x01 << 28, /* Field number */ + DITHER_ON = 0x01 << 29, /* Dithering is on */ + ROUND_ON = 0x01 << 30, /* Round is on */ + MODE_32BIT = 1UL << 31, /* Data in RGBa888, + * 0 in RGB565 + */ +}; + +#define norm_maxw() 720 +#define norm_maxh() 576 + +#define INT_ALL_STATUS (INT_FIELD_STATUS | INT_VSYNC_STATUS | \ + INT_HSYNC_STATUS | INT_VSTART_STATUS | \ + INT_DMA_END_STATUS | INT_ERROR_STATUS) + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static irqreturn_t viu_intr(int irq, void *dev_id); + +static struct viu_fmt *format_by_fourcc(int fourcc) +{ + int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].pixelformat == fourcc) + return formats + i; + } + + dprintk(0, "unknown pixelformat:'%4.4s'\n", (char *)&fourcc); + return NULL; +} + +static void viu_start_dma(struct viu_dev *dev) +{ + struct viu_reg __iomem *vr = dev->vr; + + dev->field = 0; + + /* Enable DMA operation */ + iowrite32be(SOFT_RST, &vr->status_cfg); + iowrite32be(INT_FIELD_EN, &vr->status_cfg); +} + +static void viu_stop_dma(struct viu_dev *dev) +{ + struct viu_reg __iomem *vr = dev->vr; + int cnt = 100; + u32 status_cfg; + + iowrite32be(0, &vr->status_cfg); + + /* Clear pending interrupts */ + status_cfg = ioread32be(&vr->status_cfg); + if (status_cfg & 0x3f0000) + iowrite32be(status_cfg & 0x3f0000, &vr->status_cfg); + + if (status_cfg & DMA_ACT) { + do { + status_cfg = ioread32be(&vr->status_cfg); + if (status_cfg & INT_DMA_END_STATUS) + break; + } while (cnt--); + + if (cnt < 0) { + /* timed out, issue soft reset */ + iowrite32be(SOFT_RST, &vr->status_cfg); + iowrite32be(0, &vr->status_cfg); + } else { + /* clear DMA_END and other pending irqs */ + iowrite32be(status_cfg & 0x3f0000, &vr->status_cfg); + } + } + + dev->field = 0; +} + +static int restart_video_queue(struct viu_dmaqueue *vidq) +{ + struct viu_buf *buf, *prev; + + dprintk(1, "%s vidq=%p\n", __func__, vidq); + if (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, vb.queue); + dprintk(2, "restart_queue [%p/%d]: restart dma\n", + buf, buf->vb.i); + + viu_stop_dma(vidq->dev); + + /* cancel all outstanding capture requests */ + list_for_each_entry_safe(buf, prev, &vidq->active, vb.queue) { + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + } + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + return 0; + } + + prev = NULL; + for (;;) { + if (list_empty(&vidq->queued)) + return 0; + buf = list_entry(vidq->queued.next, struct viu_buf, vb.queue); + if (prev == NULL) { + list_move_tail(&buf->vb.queue, &vidq->active); + + dprintk(1, "Restarting video dma\n"); + viu_stop_dma(vidq->dev); + viu_start_dma(vidq->dev); + + buf->vb.state = VIDEOBUF_ACTIVE; + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] restart_queue - first active\n", + buf, buf->vb.i); + + } else if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_move_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + dprintk(2, "[%p/%d] restart_queue - move to active\n", + buf, buf->vb.i); + } else { + return 0; + } + prev = buf; + } +} + +static void viu_vid_timeout(struct timer_list *t) +{ + struct viu_dev *dev = from_timer(dev, t, vidq.timeout); + struct viu_buf *buf; + struct viu_dmaqueue *vidq = &dev->vidq; + + while (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, vb.queue); + list_del(&buf->vb.queue); + buf->vb.state = VIDEOBUF_ERROR; + wake_up(&buf->vb.done); + dprintk(1, "viu/0: [%p/%d] timeout\n", buf, buf->vb.i); + } + + restart_video_queue(vidq); +} + +/* + * Videobuf operations + */ +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct viu_fh *fh = vq->priv_data; + + *size = fh->width * fh->height * fh->fmt->depth >> 3; + if (*count == 0) + *count = 32; + + while (*size * *count > VIU_VID_MEM_LIMIT * 1024 * 1024) + (*count)--; + + dprintk(1, "%s, count=%d, size=%d\n", __func__, *count, *size); + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct viu_buf *buf) +{ + struct videobuf_buffer *vb = &buf->vb; + void *vaddr = NULL; + + videobuf_waiton(vq, &buf->vb, 0, 0); + + if (vq->int_ops && vq->int_ops->vaddr) + vaddr = vq->int_ops->vaddr(vb); + + if (vaddr) + videobuf_dma_contig_free(vq, &buf->vb); + + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +inline int buffer_activate(struct viu_dev *dev, struct viu_buf *buf) +{ + struct viu_reg __iomem *vr = dev->vr; + int bpp; + + /* setup the DMA base address */ + reg_val.field_base_addr = videobuf_to_dma_contig(&buf->vb); + + dprintk(1, "buffer_activate [%p/%d]: dma addr 0x%lx\n", + buf, buf->vb.i, (unsigned long)reg_val.field_base_addr); + + /* interlace is on by default, set horizontal DMA increment */ + reg_val.status_cfg = 0; + bpp = buf->fmt->depth >> 3; + switch (bpp) { + case 2: + reg_val.status_cfg &= ~MODE_32BIT; + reg_val.dma_inc = buf->vb.width * 2; + break; + case 4: + reg_val.status_cfg |= MODE_32BIT; + reg_val.dma_inc = buf->vb.width * 4; + break; + default: + dprintk(0, "doesn't support color depth(%d)\n", + bpp * 8); + return -EINVAL; + } + + /* setup picture_count register */ + reg_val.picture_count = (buf->vb.height / 2) << 16 | + buf->vb.width; + + reg_val.status_cfg |= DMA_ACT | INT_DMA_END_EN | INT_FIELD_EN; + + buf->vb.state = VIDEOBUF_ACTIVE; + dev->capfield = buf->vb.field; + + /* reset dma increment if needed */ + if (!V4L2_FIELD_HAS_BOTH(buf->vb.field)) + reg_val.dma_inc = 0; + + iowrite32be(reg_val.dma_inc, &vr->dma_inc); + iowrite32be(reg_val.picture_count, &vr->picture_count); + iowrite32be(reg_val.field_base_addr, &vr->field_base_addr); + mod_timer(&dev->vidq.timeout, jiffies + BUFFER_TIMEOUT); + return 0; +} + +static int buffer_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct viu_fh *fh = vq->priv_data; + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + int rc; + + BUG_ON(fh->fmt == NULL); + + if (fh->width < 48 || fh->width > norm_maxw() || + fh->height < 32 || fh->height > norm_maxh()) + return -EINVAL; + buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3; + if (buf->vb.baddr != 0 && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + } + + if (buf->vb.state == VIDEOBUF_NEEDS_INIT) { + rc = videobuf_iolock(vq, &buf->vb, NULL); + if (rc != 0) + goto fail; + + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + buf->fmt = fh->fmt; + } + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + +fail: + free_buffer(vq, buf); + return rc; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + struct viu_fh *fh = vq->priv_data; + struct viu_dev *dev = fh->dev; + struct viu_dmaqueue *vidq = &dev->vidq; + struct viu_buf *prev; + + if (!list_empty(&vidq->queued)) { + dprintk(1, "adding vb queue=%p\n", &buf->vb.queue); + dprintk(1, "vidq pointer 0x%p, queued 0x%p\n", + vidq, &vidq->queued); + dprintk(1, "dev %p, queued: self %p, next %p, head %p\n", + dev, &vidq->queued, vidq->queued.next, + vidq->queued.prev); + list_add_tail(&buf->vb.queue, &vidq->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - append to queued\n", + buf, buf->vb.i); + } else if (list_empty(&vidq->active)) { + dprintk(1, "adding vb active=%p\n", &buf->vb.queue); + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); + dprintk(2, "[%p/%d] buffer_queue - first active\n", + buf, buf->vb.i); + + buffer_activate(dev, buf); + } else { + dprintk(1, "adding vb queue2=%p\n", &buf->vb.queue); + prev = list_entry(vidq->active.prev, struct viu_buf, vb.queue); + if (prev->vb.width == buf->vb.width && + prev->vb.height == buf->vb.height && + prev->fmt == buf->fmt) { + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + dprintk(2, "[%p/%d] buffer_queue - append to active\n", + buf, buf->vb.i); + } else { + list_add_tail(&buf->vb.queue, &vidq->queued); + buf->vb.state = VIDEOBUF_QUEUED; + dprintk(2, "[%p/%d] buffer_queue - first queued\n", + buf, buf->vb.i); + } + } +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct viu_buf *buf = container_of(vb, struct viu_buf, vb); + struct viu_fh *fh = vq->priv_data; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + + viu_stop_dma(dev); + free_buffer(vq, buf); +} + +static const struct videobuf_queue_ops viu_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* + * IOCTL vidioc handling + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "viu", sizeof(cap->driver)); + strscpy(cap->card, "viu", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:viu", sizeof(cap->bus_info)); + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int index = f->index; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + f->pixelformat = formats[index].fourcc; + return 0; +} + +static int vidioc_g_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vb_vidq.field; + f->fmt.pix.pixelformat = fh->fmt->pixelformat; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = fh->sizeimage; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + return 0; +} + +static int vidioc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fmt *fmt; + unsigned int maxw, maxh; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (!fmt) { + dprintk(1, "Fourcc format (0x%08x) invalid.", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + maxw = norm_maxw(); + maxh = norm_maxh(); + + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + if (f->fmt.pix.height < 32) + f->fmt.pix.height = 32; + if (f->fmt.pix.height > maxh) + f->fmt.pix.height = maxh; + if (f->fmt.pix.width < 48) + f->fmt.pix.width = 48; + if (f->fmt.pix.width > maxw) + f->fmt.pix.width = maxw; + f->fmt.pix.width &= ~0x03; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + int ret; + + ret = vidioc_try_fmt_cap(file, fh, f); + if (ret < 0) + return ret; + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->sizeimage = f->fmt.pix.sizeimage; + fh->vb_vidq.field = f->fmt.pix.field; + fh->type = f->type; + return 0; +} + +static int vidioc_g_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + + f->fmt.win = fh->win; + return 0; +} + +static int verify_preview(struct viu_dev *dev, struct v4l2_window *win) +{ + enum v4l2_field field; + int maxw, maxh; + + if (dev->ovbuf.base == NULL) + return -EINVAL; + if (dev->ovfmt == NULL) + return -EINVAL; + if (win->w.width < 48 || win->w.height < 32) + return -EINVAL; + + field = win->field; + maxw = dev->crop_current.width; + maxh = dev->crop_current.height; + + if (field == V4L2_FIELD_ANY) { + field = (win->w.height > maxh/2) + ? V4L2_FIELD_INTERLACED + : V4L2_FIELD_TOP; + } + switch (field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + maxh = maxh / 2; + break; + case V4L2_FIELD_INTERLACED: + break; + default: + return -EINVAL; + } + + win->field = field; + if (win->w.width > maxw) + win->w.width = maxw; + if (win->w.height > maxh) + win->w.height = maxh; + return 0; +} + +inline void viu_activate_overlay(struct viu_reg __iomem *vr) +{ + iowrite32be(reg_val.field_base_addr, &vr->field_base_addr); + iowrite32be(reg_val.dma_inc, &vr->dma_inc); + iowrite32be(reg_val.picture_count, &vr->picture_count); +} + +static int viu_setup_preview(struct viu_dev *dev, struct viu_fh *fh) +{ + int bpp; + + dprintk(1, "%s %dx%d\n", __func__, + fh->win.w.width, fh->win.w.height); + + reg_val.status_cfg = 0; + + /* setup window */ + reg_val.picture_count = (fh->win.w.height / 2) << 16 | + fh->win.w.width; + + /* setup color depth and dma increment */ + bpp = dev->ovfmt->depth / 8; + switch (bpp) { + case 2: + reg_val.status_cfg &= ~MODE_32BIT; + reg_val.dma_inc = fh->win.w.width * 2; + break; + case 4: + reg_val.status_cfg |= MODE_32BIT; + reg_val.dma_inc = fh->win.w.width * 4; + break; + default: + dprintk(0, "device doesn't support color depth(%d)\n", + bpp * 8); + return -EINVAL; + } + + dev->ovfield = fh->win.field; + if (!V4L2_FIELD_HAS_BOTH(dev->ovfield)) + reg_val.dma_inc = 0; + + reg_val.status_cfg |= DMA_ACT | INT_DMA_END_EN | INT_FIELD_EN; + + /* setup the base address of the overlay buffer */ + reg_val.field_base_addr = (u32)(long)dev->ovbuf.base; + + return 0; +} + +static int vidioc_s_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + unsigned long flags; + int err; + + err = verify_preview(dev, &f->fmt.win); + if (err) + return err; + + fh->win = f->fmt.win; + + spin_lock_irqsave(&dev->slock, flags); + viu_setup_preview(dev, fh); + spin_unlock_irqrestore(&dev->slock, flags); + return 0; +} + +static int vidioc_try_fmt_overlay(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; +} + +static int vidioc_overlay(struct file *file, void *priv, unsigned int on) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = (struct viu_dev *)fh->dev; + unsigned long flags; + + if (on) { + spin_lock_irqsave(&dev->slock, flags); + viu_activate_overlay(dev->vr); + dev->ovenable = 1; + + /* start dma */ + viu_start_dma(dev); + spin_unlock_irqrestore(&dev->slock, flags); + } else { + viu_stop_dma(dev); + dev->ovenable = 0; + } + + return 0; +} + +static int vidioc_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *arg) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + struct v4l2_framebuffer *fb = arg; + + *fb = dev->ovbuf; + fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; + return 0; +} + +static int vidioc_s_fbuf(struct file *file, void *priv, const struct v4l2_framebuffer *arg) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + const struct v4l2_framebuffer *fb = arg; + struct viu_fmt *fmt; + + if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) + return -EPERM; + + /* check args */ + fmt = format_by_fourcc(fb->fmt.pixelformat); + if (fmt == NULL) + return -EINVAL; + + /* ok, accept it */ + dev->ovbuf = *fb; + dev->ovfmt = fmt; + if (dev->ovbuf.fmt.bytesperline == 0) { + dev->ovbuf.fmt.bytesperline = + dev->ovbuf.fmt.width * fmt->depth / 8; + } + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct viu_fh *fh = priv; + + return videobuf_reqbufs(&fh->vb_vidq, p); +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_querybuf(&fh->vb_vidq, p); +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_qbuf(&fh->vb_vidq, p); +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct viu_fh *fh = priv; + + return videobuf_dqbuf(&fh->vb_vidq, p, + file->f_flags & O_NONBLOCK); +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct viu_fh *fh = priv; + struct viu_dev *dev = fh->dev; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (fh->type != i) + return -EINVAL; + + if (dev->ovenable) + dev->ovenable = 0; + + viu_start_dma(fh->dev); + + return videobuf_streamon(&fh->vb_vidq); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct viu_fh *fh = priv; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (fh->type != i) + return -EINVAL; + + viu_stop_dma(fh->dev); + + return videobuf_streamoff(&fh->vb_vidq); +} + +#define decoder_call(viu, o, f, args...) \ + v4l2_subdev_call(viu->decoder, o, f, ##args) + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct viu_fh *fh = priv; + + decoder_call(fh->dev, video, querystd, std_id); + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id) +{ + struct viu_fh *fh = priv; + + fh->dev->std = id; + decoder_call(fh->dev, video, s_std, id); + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct viu_fh *fh = priv; + + *std_id = fh->dev->std; + return 0; +} + +/* only one input in this driver */ +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct viu_fh *fh = priv; + + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = fh->dev->vdev->tvnorms; + strscpy(inp->name, "Camera", sizeof(inp->name)); + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct viu_fh *fh = priv; + + if (i) + return -EINVAL; + + decoder_call(fh->dev, video, s_routing, i, 0, 0); + return 0; +} + +inline void viu_activate_next_buf(struct viu_dev *dev, + struct viu_dmaqueue *viuq) +{ + struct viu_dmaqueue *vidq = viuq; + struct viu_buf *buf; + + /* launch another DMA operation for an active/queued buffer */ + if (!list_empty(&vidq->active)) { + buf = list_entry(vidq->active.next, struct viu_buf, + vb.queue); + dprintk(1, "start another queued buffer: 0x%p\n", buf); + buffer_activate(dev, buf); + } else if (!list_empty(&vidq->queued)) { + buf = list_entry(vidq->queued.next, struct viu_buf, + vb.queue); + list_del(&buf->vb.queue); + + dprintk(1, "start another queued buffer: 0x%p\n", buf); + list_add_tail(&buf->vb.queue, &vidq->active); + buf->vb.state = VIDEOBUF_ACTIVE; + buffer_activate(dev, buf); + } +} + +inline void viu_default_settings(struct viu_reg __iomem *vr) +{ + iowrite32be(0x9512A254, &vr->luminance); + iowrite32be(0x03310000, &vr->chroma_r); + iowrite32be(0x06600F38, &vr->chroma_g); + iowrite32be(0x00000409, &vr->chroma_b); + iowrite32be(0x000000ff, &vr->alpha); + iowrite32be(0x00000090, &vr->req_alarm); + dprintk(1, "status reg: 0x%08x, field base: 0x%08x\n", + ioread32be(&vr->status_cfg), ioread32be(&vr->field_base_addr)); +} + +static void viu_overlay_intr(struct viu_dev *dev, u32 status) +{ + struct viu_reg __iomem *vr = dev->vr; + + if (status & INT_DMA_END_STATUS) + dev->dma_done = 1; + + if (status & INT_FIELD_STATUS) { + if (dev->dma_done) { + u32 addr = reg_val.field_base_addr; + + dev->dma_done = 0; + if (status & FIELD_NO) + addr += reg_val.dma_inc; + + iowrite32be(addr, &vr->field_base_addr); + iowrite32be(reg_val.dma_inc, &vr->dma_inc); + iowrite32be((status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg, &vr->status_cfg); + } else if (status & INT_VSYNC_STATUS) { + iowrite32be((status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg, &vr->status_cfg); + } + } +} + +static void viu_capture_intr(struct viu_dev *dev, u32 status) +{ + struct viu_dmaqueue *vidq = &dev->vidq; + struct viu_reg __iomem *vr = dev->vr; + struct viu_buf *buf; + int field_num; + int need_two; + int dma_done = 0; + + field_num = status & FIELD_NO; + need_two = V4L2_FIELD_HAS_BOTH(dev->capfield); + + if (status & INT_DMA_END_STATUS) { + dma_done = 1; + if (((field_num == 0) && (dev->field == 0)) || + (field_num && (dev->field == 1))) + dev->field++; + } + + if (status & INT_FIELD_STATUS) { + dprintk(1, "irq: field %d, done %d\n", + !!field_num, dma_done); + if (unlikely(dev->first)) { + if (field_num == 0) { + dev->first = 0; + dprintk(1, "activate first buf\n"); + viu_activate_next_buf(dev, vidq); + } else + dprintk(1, "wait field 0\n"); + return; + } + + /* setup buffer address for next dma operation */ + if (!list_empty(&vidq->active)) { + u32 addr = reg_val.field_base_addr; + + if (field_num && need_two) { + addr += reg_val.dma_inc; + dprintk(1, "field 1, 0x%lx, dev field %d\n", + (unsigned long)addr, dev->field); + } + iowrite32be(addr, &vr->field_base_addr); + iowrite32be(reg_val.dma_inc, &vr->dma_inc); + iowrite32be((status & 0xffc0ffff) | + (status & INT_ALL_STATUS) | + reg_val.status_cfg, &vr->status_cfg); + return; + } + } + + if (dma_done && field_num && (dev->field == 2)) { + dev->field = 0; + buf = list_entry(vidq->active.next, + struct viu_buf, vb.queue); + dprintk(1, "viu/0: [%p/%d] 0x%lx/0x%lx: dma complete\n", + buf, buf->vb.i, + (unsigned long)videobuf_to_dma_contig(&buf->vb), + (unsigned long)ioread32be(&vr->field_base_addr)); + + if (waitqueue_active(&buf->vb.done)) { + list_del(&buf->vb.queue); + buf->vb.ts = ktime_get_ns(); + buf->vb.state = VIDEOBUF_DONE; + buf->vb.field_count++; + wake_up(&buf->vb.done); + } + /* activate next dma buffer */ + viu_activate_next_buf(dev, vidq); + } +} + +static irqreturn_t viu_intr(int irq, void *dev_id) +{ + struct viu_dev *dev = (struct viu_dev *)dev_id; + struct viu_reg __iomem *vr = dev->vr; + u32 status; + u32 error; + + status = ioread32be(&vr->status_cfg); + + if (status & INT_ERROR_STATUS) { + dev->irqs.error_irq++; + error = status & ERR_MASK; + if (error) + dprintk(1, "Err: error(%d), times:%d!\n", + error >> 4, dev->irqs.error_irq); + /* Clear interrupt error bit and error flags */ + iowrite32be((status & 0xffc0ffff) | INT_ERROR_STATUS, + &vr->status_cfg); + } + + if (status & INT_DMA_END_STATUS) { + dev->irqs.dma_end_irq++; + dev->dma_done = 1; + dprintk(2, "VIU DMA end interrupt times: %d\n", + dev->irqs.dma_end_irq); + } + + if (status & INT_HSYNC_STATUS) + dev->irqs.hsync_irq++; + + if (status & INT_FIELD_STATUS) { + dev->irqs.field_irq++; + dprintk(2, "VIU field interrupt times: %d\n", + dev->irqs.field_irq); + } + + if (status & INT_VSTART_STATUS) + dev->irqs.vstart_irq++; + + if (status & INT_VSYNC_STATUS) { + dev->irqs.vsync_irq++; + dprintk(2, "VIU vsync interrupt times: %d\n", + dev->irqs.vsync_irq); + } + + /* clear all pending irqs */ + status = ioread32be(&vr->status_cfg); + iowrite32be((status & 0xffc0ffff) | (status & INT_ALL_STATUS), + &vr->status_cfg); + + if (dev->ovenable) { + viu_overlay_intr(dev, status); + return IRQ_HANDLED; + } + + /* Capture mode */ + viu_capture_intr(dev, status); + return IRQ_HANDLED; +} + +/* + * File operations for the device + */ +static int viu_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct viu_dev *dev = video_get_drvdata(vdev); + struct viu_fh *fh; + struct viu_reg __iomem *vr; + int minor = vdev->minor; + u32 status_cfg; + + dprintk(1, "viu: open (minor=%d)\n", minor); + + dev->users++; + if (dev->users > 1) { + dev->users--; + return -EBUSY; + } + + vr = dev->vr; + + dprintk(1, "open minor=%d type=%s users=%d\n", minor, + v4l2_type_names[V4L2_BUF_TYPE_VIDEO_CAPTURE], dev->users); + + if (mutex_lock_interruptible(&dev->lock)) { + dev->users--; + return -ERESTARTSYS; + } + + /* allocate and initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) { + dev->users--; + mutex_unlock(&dev->lock); + return -ENOMEM; + } + + v4l2_fh_init(&fh->fh, vdev); + file->private_data = fh; + fh->dev = dev; + + fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fh->fmt = format_by_fourcc(V4L2_PIX_FMT_RGB32); + fh->width = norm_maxw(); + fh->height = norm_maxh(); + dev->crop_current.width = fh->width; + dev->crop_current.height = fh->height; + + dprintk(1, "Open: fh=%p, dev=%p, dev->vidq=%p\n", fh, dev, &dev->vidq); + dprintk(1, "Open: list_empty queued=%d\n", + list_empty(&dev->vidq.queued)); + dprintk(1, "Open: list_empty active=%d\n", + list_empty(&dev->vidq.active)); + + viu_default_settings(vr); + + status_cfg = ioread32be(&vr->status_cfg); + iowrite32be(status_cfg & ~(INT_VSYNC_EN | INT_HSYNC_EN | + INT_FIELD_EN | INT_VSTART_EN | + INT_DMA_END_EN | INT_ERROR_EN | INT_ECC_EN), + &vr->status_cfg); + + status_cfg = ioread32be(&vr->status_cfg); + iowrite32be(status_cfg | INT_ALL_STATUS, &vr->status_cfg); + + spin_lock_init(&fh->vbq_lock); + videobuf_queue_dma_contig_init(&fh->vb_vidq, &viu_video_qops, + dev->dev, &fh->vbq_lock, + fh->type, V4L2_FIELD_INTERLACED, + sizeof(struct viu_buf), fh, + &fh->dev->lock); + v4l2_fh_add(&fh->fh); + mutex_unlock(&dev->lock); + return 0; +} + +static ssize_t viu_read(struct file *file, char __user *data, size_t count, + loff_t *ppos) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int ret = 0; + + dprintk(2, "%s\n", __func__); + if (dev->ovenable) + dev->ovenable = 0; + + if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + viu_start_dma(dev); + ret = videobuf_read_stream(&fh->vb_vidq, data, count, + ppos, 0, file->f_flags & O_NONBLOCK); + mutex_unlock(&dev->lock); + return ret; + } + return 0; +} + +static __poll_t viu_poll(struct file *file, struct poll_table_struct *wait) +{ + struct viu_fh *fh = file->private_data; + struct videobuf_queue *q = &fh->vb_vidq; + struct viu_dev *dev = fh->dev; + __poll_t req_events = poll_requested_events(wait); + __poll_t res = v4l2_ctrl_poll(file, wait); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) + return EPOLLERR; + + if (!(req_events & (EPOLLIN | EPOLLRDNORM))) + return res; + + mutex_lock(&dev->lock); + res |= videobuf_poll_stream(file, q, wait); + mutex_unlock(&dev->lock); + return res; +} + +static int viu_release(struct file *file) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int minor = video_devdata(file)->minor; + + mutex_lock(&dev->lock); + viu_stop_dma(dev); + videobuf_stop(&fh->vb_vidq); + videobuf_mmap_free(&fh->vb_vidq); + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + mutex_unlock(&dev->lock); + + kfree(fh); + + dev->users--; + dprintk(1, "close (minor=%d, users=%d)\n", + minor, dev->users); + return 0; +} + +static void viu_reset(struct viu_reg __iomem *reg) +{ + iowrite32be(0, ®->status_cfg); + iowrite32be(0x9512a254, ®->luminance); + iowrite32be(0x03310000, ®->chroma_r); + iowrite32be(0x06600f38, ®->chroma_g); + iowrite32be(0x00000409, ®->chroma_b); + iowrite32be(0, ®->field_base_addr); + iowrite32be(0, ®->dma_inc); + iowrite32be(0x01e002d0, ®->picture_count); + iowrite32be(0x00000090, ®->req_alarm); + iowrite32be(0x000000ff, ®->alpha); +} + +static int viu_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct viu_fh *fh = file->private_data; + struct viu_dev *dev = fh->dev; + int ret; + + dprintk(1, "mmap called, vma=%p\n", vma); + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + ret = videobuf_mmap_mapper(&fh->vb_vidq, vma); + mutex_unlock(&dev->lock); + + dprintk(1, "vma start=0x%08lx, size=%ld, ret=%d\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end-(unsigned long)vma->vm_start, + ret); + + return ret; +} + +static const struct v4l2_file_operations viu_fops = { + .owner = THIS_MODULE, + .open = viu_open, + .release = viu_release, + .read = viu_read, + .poll = viu_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = viu_mmap, +}; + +static const struct v4l2_ioctl_ops viu_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap, + .vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt, + .vidioc_g_fmt_vid_overlay = vidioc_g_fmt_overlay, + .vidioc_try_fmt_vid_overlay = vidioc_try_fmt_overlay, + .vidioc_s_fmt_vid_overlay = vidioc_s_fmt_overlay, + .vidioc_overlay = vidioc_overlay, + .vidioc_g_fbuf = vidioc_g_fbuf, + .vidioc_s_fbuf = vidioc_s_fbuf, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_querystd = vidioc_querystd, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device viu_template = { + .name = "FSL viu", + .fops = &viu_fops, + .minor = -1, + .ioctl_ops = &viu_ioctl_ops, + .release = video_device_release, + + .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_READWRITE, +}; + +static int viu_of_probe(struct platform_device *op) +{ + struct viu_dev *viu_dev; + struct video_device *vdev; + struct resource r; + struct viu_reg __iomem *viu_regs; + struct i2c_adapter *ad; + int ret, viu_irq; + struct clk *clk; + + ret = of_address_to_resource(op->dev.of_node, 0, &r); + if (ret) { + dev_err(&op->dev, "Can't parse device node resource\n"); + return -ENODEV; + } + + viu_irq = irq_of_parse_and_map(op->dev.of_node, 0); + if (!viu_irq) { + dev_err(&op->dev, "Error while mapping the irq\n"); + return -EINVAL; + } + + /* request mem region */ + if (!devm_request_mem_region(&op->dev, r.start, + sizeof(struct viu_reg), DRV_NAME)) { + dev_err(&op->dev, "Error while requesting mem region\n"); + ret = -EBUSY; + goto err_irq; + } + + /* remap registers */ + viu_regs = devm_ioremap(&op->dev, r.start, sizeof(struct viu_reg)); + if (!viu_regs) { + dev_err(&op->dev, "Can't map register set\n"); + ret = -ENOMEM; + goto err_irq; + } + + /* Prepare our private structure */ + viu_dev = devm_kzalloc(&op->dev, sizeof(struct viu_dev), GFP_KERNEL); + if (!viu_dev) { + dev_err(&op->dev, "Can't allocate private structure\n"); + ret = -ENOMEM; + goto err_irq; + } + + viu_dev->vr = viu_regs; + viu_dev->irq = viu_irq; + viu_dev->dev = &op->dev; + + /* init video dma queues */ + INIT_LIST_HEAD(&viu_dev->vidq.active); + INIT_LIST_HEAD(&viu_dev->vidq.queued); + + snprintf(viu_dev->v4l2_dev.name, + sizeof(viu_dev->v4l2_dev.name), "%s", "VIU"); + ret = v4l2_device_register(viu_dev->dev, &viu_dev->v4l2_dev); + if (ret < 0) { + dev_err(&op->dev, "v4l2_device_register() failed: %d\n", ret); + goto err_irq; + } + + ad = i2c_get_adapter(0); + if (!ad) { + ret = -EFAULT; + dev_err(&op->dev, "couldn't get i2c adapter\n"); + goto err_v4l2; + } + + v4l2_ctrl_handler_init(&viu_dev->hdl, 5); + if (viu_dev->hdl.error) { + ret = viu_dev->hdl.error; + dev_err(&op->dev, "couldn't register control\n"); + goto err_i2c; + } + /* This control handler will inherit the control(s) from the + sub-device(s). */ + viu_dev->v4l2_dev.ctrl_handler = &viu_dev->hdl; + viu_dev->decoder = v4l2_i2c_new_subdev(&viu_dev->v4l2_dev, ad, + "saa7113", VIU_VIDEO_DECODER_ADDR, NULL); + + timer_setup(&viu_dev->vidq.timeout, viu_vid_timeout, 0); + viu_dev->std = V4L2_STD_NTSC_M; + viu_dev->first = 1; + + /* Allocate memory for video device */ + vdev = video_device_alloc(); + if (vdev == NULL) { + ret = -ENOMEM; + goto err_hdl; + } + + *vdev = viu_template; + + vdev->v4l2_dev = &viu_dev->v4l2_dev; + + viu_dev->vdev = vdev; + + /* initialize locks */ + mutex_init(&viu_dev->lock); + viu_dev->vdev->lock = &viu_dev->lock; + spin_lock_init(&viu_dev->slock); + + video_set_drvdata(viu_dev->vdev, viu_dev); + + mutex_lock(&viu_dev->lock); + + ret = video_register_device(viu_dev->vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + video_device_release(viu_dev->vdev); + goto err_unlock; + } + + /* enable VIU clock */ + clk = devm_clk_get(&op->dev, "ipg"); + if (IS_ERR(clk)) { + dev_err(&op->dev, "failed to lookup the clock!\n"); + ret = PTR_ERR(clk); + goto err_vdev; + } + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&op->dev, "failed to enable the clock!\n"); + goto err_vdev; + } + viu_dev->clk = clk; + + /* reset VIU module */ + viu_reset(viu_dev->vr); + + /* install interrupt handler */ + if (request_irq(viu_dev->irq, viu_intr, 0, "viu", (void *)viu_dev)) { + dev_err(&op->dev, "Request VIU IRQ failed.\n"); + ret = -ENODEV; + goto err_clk; + } + + mutex_unlock(&viu_dev->lock); + + dev_info(&op->dev, "Freescale VIU Video Capture Board\n"); + return ret; + +err_clk: + clk_disable_unprepare(viu_dev->clk); +err_vdev: + video_unregister_device(viu_dev->vdev); +err_unlock: + mutex_unlock(&viu_dev->lock); +err_hdl: + v4l2_ctrl_handler_free(&viu_dev->hdl); +err_i2c: + i2c_put_adapter(ad); +err_v4l2: + v4l2_device_unregister(&viu_dev->v4l2_dev); +err_irq: + irq_dispose_mapping(viu_irq); + return ret; +} + +static int viu_of_remove(struct platform_device *op) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(op); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + struct v4l2_subdev *sdev = list_entry(v4l2_dev->subdevs.next, + struct v4l2_subdev, list); + struct i2c_client *client = v4l2_get_subdevdata(sdev); + + free_irq(dev->irq, (void *)dev); + irq_dispose_mapping(dev->irq); + + clk_disable_unprepare(dev->clk); + + v4l2_ctrl_handler_free(&dev->hdl); + video_unregister_device(dev->vdev); + i2c_put_adapter(client->adapter); + v4l2_device_unregister(&dev->v4l2_dev); + return 0; +} + +#ifdef CONFIG_PM +static int viu_suspend(struct platform_device *op, pm_message_t state) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(op); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + + clk_disable(dev->clk); + return 0; +} + +static int viu_resume(struct platform_device *op) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(op); + struct viu_dev *dev = container_of(v4l2_dev, struct viu_dev, v4l2_dev); + + clk_enable(dev->clk); + return 0; +} +#endif + +/* + * Initialization and module stuff + */ +static const struct of_device_id mpc512x_viu_of_match[] = { + { + .compatible = "fsl,mpc5121-viu", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc512x_viu_of_match); + +static struct platform_driver viu_of_platform_driver = { + .probe = viu_of_probe, + .remove = viu_of_remove, +#ifdef CONFIG_PM + .suspend = viu_suspend, + .resume = viu_resume, +#endif + .driver = { + .name = DRV_NAME, + .of_match_table = mpc512x_viu_of_match, + }, +}; + +module_platform_driver(viu_of_platform_driver); + +MODULE_DESCRIPTION("Freescale Video-In(VIU)"); +MODULE_AUTHOR("Hongjun Chen"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VIU_VERSION); diff --git a/drivers/media/platform/nxp/imx-jpeg/Kconfig b/drivers/media/platform/nxp/imx-jpeg/Kconfig new file mode 100644 index 000000000000..cbf6101a8b1d --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +config VIDEO_IMX8_JPEG + tristate "IMX8 JPEG Encoder/Decoder" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV && VIDEO_V4L2 + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select V4L2_JPEG_HELPER + help + This is a video4linux2 driver for the i.MX8 QXP/QM integrated + JPEG encoder/decoder. diff --git a/drivers/media/platform/nxp/imx-jpeg/Makefile b/drivers/media/platform/nxp/imx-jpeg/Makefile new file mode 100644 index 000000000000..bf19c82e61b4 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +mxc-jpeg-encdec-objs := mxc-jpeg-hw.o mxc-jpeg.o +obj-$(CONFIG_VIDEO_IMX8_JPEG) += mxc-jpeg-encdec.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c new file mode 100644 index 000000000000..29c604b1b179 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include <linux/delay.h> +#include <media/videobuf2-core.h> +#include "mxc-jpeg-hw.h" + +#define print_wrapper_reg(dev, base_address, reg_offset)\ + internal_print_wrapper_reg(dev, (base_address), #reg_offset,\ + (reg_offset)) +#define internal_print_wrapper_reg(dev, base_address, reg_name, reg_offset) {\ + int val;\ + val = readl((base_address) + (reg_offset));\ + dev_dbg(dev, "Wrapper reg %s = 0x%x\n", reg_name, val);\ +} + +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc) +{ + dev_dbg(dev, " MXC JPEG NEXT_DESCPT_PTR 0x%x\n", + desc->next_descpt_ptr); + dev_dbg(dev, " MXC JPEG BUF_BASE0 0x%x\n", desc->buf_base0); + dev_dbg(dev, " MXC JPEG BUF_BASE1 0x%x\n", desc->buf_base1); + dev_dbg(dev, " MXC JPEG LINE_PITCH %d\n", desc->line_pitch); + dev_dbg(dev, " MXC JPEG STM_BUFBASE 0x%x\n", desc->stm_bufbase); + dev_dbg(dev, " MXC JPEG STM_BUFSIZE %d\n", desc->stm_bufsize); + dev_dbg(dev, " MXC JPEG IMGSIZE %x (%d x %d)\n", desc->imgsize, + desc->imgsize >> 16, desc->imgsize & 0xFFFF); + dev_dbg(dev, " MXC JPEG STM_CTRL 0x%x\n", desc->stm_ctrl); +} + +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode) +{ + dev_dbg(dev, "CAST IP status regs:\n"); + print_wrapper_reg(dev, reg, CAST_STATUS0); + print_wrapper_reg(dev, reg, CAST_STATUS1); + print_wrapper_reg(dev, reg, CAST_STATUS2); + print_wrapper_reg(dev, reg, CAST_STATUS3); + print_wrapper_reg(dev, reg, CAST_STATUS4); + print_wrapper_reg(dev, reg, CAST_STATUS5); + print_wrapper_reg(dev, reg, CAST_STATUS6); + print_wrapper_reg(dev, reg, CAST_STATUS7); + print_wrapper_reg(dev, reg, CAST_STATUS8); + print_wrapper_reg(dev, reg, CAST_STATUS9); + print_wrapper_reg(dev, reg, CAST_STATUS10); + print_wrapper_reg(dev, reg, CAST_STATUS11); + print_wrapper_reg(dev, reg, CAST_STATUS12); + print_wrapper_reg(dev, reg, CAST_STATUS13); + if (mode == MXC_JPEG_DECODE) + return; + print_wrapper_reg(dev, reg, CAST_STATUS14); + print_wrapper_reg(dev, reg, CAST_STATUS15); + print_wrapper_reg(dev, reg, CAST_STATUS16); + print_wrapper_reg(dev, reg, CAST_STATUS17); + print_wrapper_reg(dev, reg, CAST_STATUS18); + print_wrapper_reg(dev, reg, CAST_STATUS19); +} + +void print_wrapper_info(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "Wrapper regs:\n"); + print_wrapper_reg(dev, reg, GLB_CTRL); + print_wrapper_reg(dev, reg, COM_STATUS); + print_wrapper_reg(dev, reg, BUF_BASE0); + print_wrapper_reg(dev, reg, BUF_BASE1); + print_wrapper_reg(dev, reg, LINE_PITCH); + print_wrapper_reg(dev, reg, STM_BUFBASE); + print_wrapper_reg(dev, reg, STM_BUFSIZE); + print_wrapper_reg(dev, reg, IMGSIZE); + print_wrapper_reg(dev, reg, STM_CTRL); +} + +void mxc_jpeg_enable_irq(void __iomem *reg, int slot) +{ + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); +} + +void mxc_jpeg_sw_reset(void __iomem *reg) +{ + /* + * engine soft reset, internal state machine reset + * this will not reset registers, however, it seems + * the registers may remain inconsistent with the internal state + * so, on purpose, at least let GLB_CTRL bits clear after this reset + */ + writel(GLB_CTRL_SFT_RST, reg + GLB_CTRL); +} + +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Encoder CONFIG...\n"); + /* + * "Config_Mode" enabled, "Config_Mode auto clear enabled", + */ + writel(0xa0, reg + CAST_MODE); + + /* all markers and segments */ + writel(0x3ff, reg + CAST_CFG_MODE); + + /* quality factor */ + writel(0x4b, reg + CAST_QUALITY); +} + +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Encoder GO...\n"); + /* + * "GO" enabled, "GO bit auto clear" enabled + */ + writel(0x140, reg + CAST_MODE); +} + +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Decoder GO...\n"); + writel(MXC_DEC_EXIT_IDLE_MODE, reg + CAST_CTRL); +} + +int mxc_jpeg_enable(void __iomem *reg) +{ + u32 regval; + + writel(GLB_CTRL_JPG_EN, reg + GLB_CTRL); + regval = readl(reg); + return regval; +} + +void mxc_jpeg_enable_slot(void __iomem *reg, int slot) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + writel(GLB_CTRL_SLOT_EN(slot) | regval, reg + GLB_CTRL); +} + +void mxc_jpeg_set_l_endian(void __iomem *reg, int le) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + regval &= ~GLB_CTRL_L_ENDIAN(1); /* clear */ + writel(GLB_CTRL_L_ENDIAN(le) | regval, reg + GLB_CTRL); /* set */ +} + +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize) +{ + desc->stm_bufsize = bufsize; +} + +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h) +{ + desc->imgsize = w << 16 | h; +} + +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch) +{ + desc->line_pitch = line_pitch; +} + +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot) +{ + writel(desc | MXC_NXT_DESCPT_EN, + reg + MXC_SLOT_OFFSET(slot, SLOT_NXT_DESCPT_PTR)); +} diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h new file mode 100644 index 000000000000..ae70d3a0dc24 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#ifndef _MXC_JPEG_HW_H +#define _MXC_JPEG_HW_H + +/* JPEG Decoder/Encoder Wrapper Register Map */ +#define GLB_CTRL 0x0 +#define COM_STATUS 0x4 +#define BUF_BASE0 0x14 +#define BUF_BASE1 0x18 +#define LINE_PITCH 0x1C +#define STM_BUFBASE 0x20 +#define STM_BUFSIZE 0x24 +#define IMGSIZE 0x28 +#define STM_CTRL 0x2C + +/* CAST JPEG-Decoder/Encoder Status Register Map (read-only)*/ +#define CAST_STATUS0 0x100 +#define CAST_STATUS1 0x104 +#define CAST_STATUS2 0x108 +#define CAST_STATUS3 0x10c +#define CAST_STATUS4 0x110 +#define CAST_STATUS5 0x114 +#define CAST_STATUS6 0x118 +#define CAST_STATUS7 0x11c +#define CAST_STATUS8 0x120 +#define CAST_STATUS9 0x124 +#define CAST_STATUS10 0x128 +#define CAST_STATUS11 0x12c +#define CAST_STATUS12 0x130 +#define CAST_STATUS13 0x134 +/* the following are for encoder only */ +#define CAST_STATUS14 0x138 +#define CAST_STATUS15 0x13c +#define CAST_STATUS16 0x140 +#define CAST_STATUS17 0x144 +#define CAST_STATUS18 0x148 +#define CAST_STATUS19 0x14c + +/* CAST JPEG-Decoder Control Register Map (write-only) */ +#define CAST_CTRL CAST_STATUS13 + +/* CAST JPEG-Encoder Control Register Map (write-only) */ +#define CAST_MODE CAST_STATUS0 +#define CAST_CFG_MODE CAST_STATUS1 +#define CAST_QUALITY CAST_STATUS2 +#define CAST_RSVD CAST_STATUS3 +#define CAST_REC_REGS_SEL CAST_STATUS4 +#define CAST_LUMTH CAST_STATUS5 +#define CAST_CHRTH CAST_STATUS6 +#define CAST_NOMFRSIZE_LO CAST_STATUS7 +#define CAST_NOMFRSIZE_HI CAST_STATUS8 +#define CAST_OFBSIZE_LO CAST_STATUS9 +#define CAST_OFBSIZE_HI CAST_STATUS10 + +#define MXC_MAX_SLOTS 1 /* TODO use all 4 slots*/ +/* JPEG-Decoder Wrapper Slot Registers 0..3 */ +#define SLOT_BASE 0x10000 +#define SLOT_STATUS 0x0 +#define SLOT_IRQ_EN 0x4 +#define SLOT_BUF_PTR 0x8 +#define SLOT_CUR_DESCPT_PTR 0xC +#define SLOT_NXT_DESCPT_PTR 0x10 +#define MXC_SLOT_OFFSET(slot, offset) ((SLOT_BASE * ((slot) + 1)) + (offset)) + +/* GLB_CTRL fields */ +#define GLB_CTRL_JPG_EN 0x1 +#define GLB_CTRL_SFT_RST (0x1 << 1) +#define GLB_CTRL_DEC_GO (0x1 << 2) +#define GLB_CTRL_L_ENDIAN(le) ((le) << 3) +#define GLB_CTRL_SLOT_EN(slot) (0x1 << ((slot) + 4)) + +/* COM_STAUS fields */ +#define COM_STATUS_DEC_ONGOING(r) (((r) & (1 << 31)) >> 31) +#define COM_STATUS_CUR_SLOT(r) (((r) & (0x3 << 29)) >> 29) + +/* STM_CTRL fields */ +#define STM_CTRL_PIXEL_PRECISION (0x1 << 2) +#define STM_CTRL_IMAGE_FORMAT(img_fmt) ((img_fmt) << 3) +#define STM_CTRL_IMAGE_FORMAT_MASK (0xF << 3) +#define STM_CTRL_BITBUF_PTR_CLR(clr) ((clr) << 7) +#define STM_CTRL_AUTO_START(go) ((go) << 8) +#define STM_CTRL_CONFIG_MOD(mod) ((mod) << 9) + +/* SLOT_STATUS fields for slots 0..3 */ +#define SLOT_STATUS_FRMDONE (0x1 << 3) +#define SLOT_STATUS_ENC_CONFIG_ERR (0x1 << 8) + +/* SLOT_IRQ_EN fields TBD */ + +#define MXC_NXT_DESCPT_EN 0x1 +#define MXC_DEC_EXIT_IDLE_MODE 0x4 + +/* JPEG-Decoder Wrapper - STM_CTRL Register Fields */ +#define MXC_PIXEL_PRECISION(precision) ((precision) / 8 << 2) +enum mxc_jpeg_image_format { + MXC_JPEG_INVALID = -1, + MXC_JPEG_YUV420 = 0x0, /* 2 Plannar, Y=1st plane UV=2nd plane */ + MXC_JPEG_YUV422 = 0x1, /* 1 Plannar, YUYV sequence */ + MXC_JPEG_RGB = 0x2, /* RGBRGB packed format */ + MXC_JPEG_YUV444 = 0x3, /* 1 Plannar, YUVYUV sequence */ + MXC_JPEG_GRAY = 0x4, /* Y8 or Y12 or Single Component */ + MXC_JPEG_RESERVED = 0x5, + MXC_JPEG_ARGB = 0x6, +}; + +#include "mxc-jpeg.h" +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc); +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode); +void print_wrapper_info(struct device *dev, void __iomem *reg); +void mxc_jpeg_sw_reset(void __iomem *reg); +int mxc_jpeg_enable(void __iomem *reg); +void wait_frmdone(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg); +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg); +int mxc_jpeg_get_slot(void __iomem *reg); +u32 mxc_jpeg_get_offset(void __iomem *reg, int slot); +void mxc_jpeg_enable_slot(void __iomem *reg, int slot); +void mxc_jpeg_set_l_endian(void __iomem *reg, int le); +void mxc_jpeg_enable_irq(void __iomem *reg, int slot); +int mxc_jpeg_set_input(void __iomem *reg, u32 in_buf, u32 bufsize); +int mxc_jpeg_set_output(void __iomem *reg, u16 out_pitch, u32 out_buf, + u16 w, u16 h); +void mxc_jpeg_set_config_mode(void __iomem *reg, int config_mode); +int mxc_jpeg_set_params(struct mxc_jpeg_desc *desc, u32 bufsize, u16 + out_pitch, u32 format); +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize); +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h); +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch); +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot); +void mxc_jpeg_set_regs_from_desc(struct mxc_jpeg_desc *desc, + void __iomem *reg); +#endif diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c new file mode 100644 index 000000000000..3b892c5792b4 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -0,0 +1,2254 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 driver for the JPEG encoder/decoder from i.MX8QXP/i.MX8QM application + * processors. + * + * The multi-planar buffers API is used. + * + * Baseline and extended sequential jpeg decoding is supported. + * Progressive jpeg decoding is not supported by the IP. + * Supports encode and decode of various formats: + * YUV444, YUV422, YUV420, RGB, ARGB, Gray + * YUV420 is the only multi-planar format supported. + * Minimum resolution is 64 x 64, maximum 8192 x 8192. + * To achieve 8192 x 8192, modify in defconfig: CONFIG_CMA_SIZE_MBYTES=320 + * The alignment requirements for the resolution depend on the format, + * multiple of 16 resolutions should work for all formats. + * Special workarounds are made in the driver to support NV12 1080p. + * When decoding, the driver detects image resolution and pixel format + * from the jpeg stream, by parsing the jpeg markers. + * + * The IP has 4 slots available for context switching, but only slot 0 + * was fully tested to work. Context switching is not used by the driver. + * Each driver instance (context) allocates a slot for itself, but this + * is postponed until device_run, to allow unlimited opens. + * + * The driver submits jobs to the IP by setting up a descriptor for the + * used slot, and then validating it. The encoder has an additional descriptor + * for the configuration phase. The driver expects FRM_DONE interrupt from + * IP to mark the job as finished. + * + * The decoder IP has some limitations regarding the component ID's, + * but the driver works around this by replacing them in the jpeg stream. + * + * A module parameter is available for debug purpose (jpeg_tracing), to enable + * it, enable dynamic debug for this module and: + * echo 1 > /sys/module/mxc_jpeg_encdec/parameters/jpeg_tracing + * + * This is inspired by the drivers/media/platform/s5p-jpeg driver + * + * Copyright 2018-2019 NXP + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/irqreturn.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/string.h> + +#include <media/v4l2-jpeg.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "mxc-jpeg-hw.h" +#include "mxc-jpeg.h" + +static const struct mxc_jpeg_fmt mxc_formats[] = { + { + .name = "JPEG", + .fourcc = V4L2_PIX_FMT_JPEG, + .subsampling = -1, + .nc = -1, + .colplanes = 1, + .flags = MXC_JPEG_FMT_TYPE_ENC, + }, + { + .name = "RGB", /*RGBRGB packed format*/ + .fourcc = V4L2_PIX_FMT_RGB24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, + { + .name = "ARGB", /* ARGBARGB packed format */ + .fourcc = V4L2_PIX_FMT_ARGB32, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 4, + .depth = 32, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12M, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .colplanes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, + { + .name = "YUV422", /* YUYV */ + .fourcc = V4L2_PIX_FMT_YUYV, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + .nc = 3, + .depth = 16, + .colplanes = 1, + .h_align = 4, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, + { + .name = "YUV444", /* YUVYUV */ + .fourcc = V4L2_PIX_FMT_YUV24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, + { + .name = "Gray", /* Gray (Y8/Y12) or Single Comp */ + .fourcc = V4L2_PIX_FMT_GREY, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + .nc = 1, + .depth = 8, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + }, +}; + +#define MXC_JPEG_NUM_FORMATS ARRAY_SIZE(mxc_formats) + +static const int mxc_decode_mode = MXC_JPEG_DECODE; +static const int mxc_encode_mode = MXC_JPEG_ENCODE; + +static const struct of_device_id mxc_jpeg_match[] = { + { + .compatible = "nxp,imx8qxp-jpgdec", + .data = &mxc_decode_mode, + }, + { + .compatible = "nxp,imx8qxp-jpgenc", + .data = &mxc_encode_mode, + }, + { }, +}; + +/* + * default configuration stream, 64x64 yuv422 + * split by JPEG marker, so it's easier to modify & use + */ +static const unsigned char jpeg_soi[] = { + 0xFF, 0xD8 +}; + +static const unsigned char jpeg_app0[] = { + 0xFF, 0xE0, + 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00 +}; + +static const unsigned char jpeg_app14[] = { + 0xFF, 0xEE, + 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const unsigned char jpeg_dqt[] = { + 0xFF, 0xDB, + 0x00, 0x84, 0x00, 0x10, 0x0B, 0x0C, 0x0E, + 0x0C, 0x0A, 0x10, 0x0E, 0x0D, 0x0E, 0x12, + 0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, + 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, 0x1D, + 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, + 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, + 0x51, 0x57, 0x5F, 0x62, 0x67, 0x68, 0x67, + 0x3E, 0x4D, 0x71, 0x79, 0x70, 0x64, 0x78, + 0x5C, 0x65, 0x67, 0x63, 0x01, 0x11, 0x12, + 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, + 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const unsigned char jpeg_sof_maximal[] = { + 0xFF, 0xC0, + 0x00, 0x14, 0x08, 0x00, 0x40, 0x00, 0x40, + 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01, 0x04, 0x11, 0x01 +}; + +static const unsigned char jpeg_dht[] = { + 0xFF, 0xC4, + 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, + 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, + 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, + 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, + 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, + 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, + 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, + 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, + 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, + 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, + 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, + 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, + 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, + 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; + +static const unsigned char jpeg_dri[] = { + 0xFF, 0xDD, + 0x00, 0x04, 0x00, 0x20 +}; + +static const unsigned char jpeg_sos_maximal[] = { + 0xFF, 0xDA, + 0x00, 0x0C, 0x04, 0x01, 0x00, 0x02, 0x11, 0x03, + 0x11, 0x04, 0x11, 0x00, 0x3F, 0x00 +}; + +static const unsigned char jpeg_image_red[] = { + 0xFC, 0x5F, 0xA2, 0xBF, 0xCA, 0x73, 0xFE, 0xFE, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00 +}; + +static const unsigned char jpeg_eoi[] = { + 0xFF, 0xD9 +}; + +struct mxc_jpeg_src_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer b; + struct list_head list; + + /* mxc-jpeg specific */ + bool dht_needed; + bool jpeg_parse_error; +}; + +static inline struct mxc_jpeg_src_buf *vb2_to_mxc_buf(struct vb2_buffer *vb) +{ + return container_of(to_vb2_v4l2_buffer(vb), + struct mxc_jpeg_src_buf, b); +} + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-3)"); + +static void _bswap16(u16 *a) +{ + *a = ((*a & 0x00FF) << 8) | ((*a & 0xFF00) >> 8); +} + +static void print_mxc_buf(struct mxc_jpeg_dev *jpeg, struct vb2_buffer *buf, + unsigned long len) +{ + unsigned int plane_no; + u32 dma_addr; + void *vaddr; + unsigned long payload; + + if (debug < 3) + return; + + for (plane_no = 0; plane_no < buf->num_planes; plane_no++) { + payload = vb2_get_plane_payload(buf, plane_no); + if (len == 0) + len = payload; + dma_addr = vb2_dma_contig_plane_dma_addr(buf, plane_no); + vaddr = vb2_plane_vaddr(buf, plane_no); + v4l2_dbg(3, debug, &jpeg->v4l2_dev, + "plane %d (vaddr=%p dma_addr=%x payload=%ld):", + plane_no, vaddr, dma_addr, payload); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + vaddr, len, false); + } +} + +static inline struct mxc_jpeg_ctx *mxc_jpeg_fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mxc_jpeg_ctx, fh); +} + +static int enum_fmt(const struct mxc_jpeg_fmt *mxc_formats, int n, + struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + + for (i = 0; i < n; ++i) { + if (mxc_formats[i].flags == type) { + /* index-th format of searched type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + /* Format not found */ + if (i >= n) + return -EINVAL; + + strscpy(f->description, mxc_formats[i].name, sizeof(f->description)); + f->pixelformat = mxc_formats[i].fourcc; + + return 0; +} + +static const struct mxc_jpeg_fmt *mxc_jpeg_find_format(struct mxc_jpeg_ctx *ctx, + u32 pixelformat) +{ + unsigned int k; + + for (k = 0; k < MXC_JPEG_NUM_FORMATS; k++) { + const struct mxc_jpeg_fmt *fmt = &mxc_formats[k]; + + if (fmt->fourcc == pixelformat) + return fmt; + } + return NULL; +} + +static enum mxc_jpeg_image_format mxc_jpeg_fourcc_to_imgfmt(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_GREY: + return MXC_JPEG_GRAY; + case V4L2_PIX_FMT_YUYV: + return MXC_JPEG_YUV422; + case V4L2_PIX_FMT_NV12M: + return MXC_JPEG_YUV420; + case V4L2_PIX_FMT_YUV24: + return MXC_JPEG_YUV444; + case V4L2_PIX_FMT_RGB24: + return MXC_JPEG_RGB; + case V4L2_PIX_FMT_ARGB32: + return MXC_JPEG_ARGB; + default: + return MXC_JPEG_INVALID; + } +} + +static struct mxc_jpeg_q_data *mxc_jpeg_get_q_data(struct mxc_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + return &ctx->cap_q; +} + +static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, + struct vb2_buffer *raw_buf, + struct vb2_buffer *jpeg_buf, int offset) +{ + int img_fmt = desc->stm_ctrl & STM_CTRL_IMAGE_FORMAT_MASK; + + desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); + desc->buf_base1 = 0; + if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { + WARN_ON(raw_buf->num_planes < 2); + desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + } + desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + + offset; +} + +static void notify_eos(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event EOS reached"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static void notify_src_chg(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event SRC_CH_RESOLUTION"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static int mxc_get_free_slot(struct mxc_jpeg_slot_data slot_data[], int n) +{ + int free_slot = 0; + + while (slot_data[free_slot].used && free_slot < n) + free_slot++; + + return free_slot; /* >=n when there are no more free slots */ +} + +static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg, + unsigned int slot) +{ + struct mxc_jpeg_desc *desc; + struct mxc_jpeg_desc *cfg_desc; + void *cfg_stm; + + if (jpeg->slot_data[slot].desc) + goto skip_alloc; /* already allocated, reuse it */ + + /* allocate descriptor for decoding/encoding phase */ + desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data[slot].desc_handle, + GFP_ATOMIC); + if (!desc) + goto err; + jpeg->slot_data[slot].desc = desc; + + /* allocate descriptor for configuration phase (encoder only) */ + cfg_desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data[slot].cfg_desc_handle, + GFP_ATOMIC); + if (!cfg_desc) + goto err; + jpeg->slot_data[slot].cfg_desc = cfg_desc; + + /* allocate configuration stream */ + cfg_stm = dma_alloc_coherent(jpeg->dev, + MXC_JPEG_MAX_CFG_STREAM, + &jpeg->slot_data[slot].cfg_stream_handle, + GFP_ATOMIC); + if (!cfg_stm) + goto err; + jpeg->slot_data[slot].cfg_stream_vaddr = cfg_stm; + +skip_alloc: + jpeg->slot_data[slot].used = true; + + return true; +err: + dev_err(jpeg->dev, "Could not allocate descriptors for slot %d", slot); + + return false; +} + +static void mxc_jpeg_free_slot_data(struct mxc_jpeg_dev *jpeg, + unsigned int slot) +{ + if (slot >= MXC_MAX_SLOTS) { + dev_err(jpeg->dev, "Invalid slot %d, nothing to free.", slot); + return; + } + + /* free descriptor for decoding/encoding phase */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data[slot].desc, + jpeg->slot_data[slot].desc_handle); + + /* free descriptor for encoder configuration phase / decoder DHT */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data[slot].cfg_desc, + jpeg->slot_data[slot].cfg_desc_handle); + + /* free configuration stream */ + dma_free_coherent(jpeg->dev, MXC_JPEG_MAX_CFG_STREAM, + jpeg->slot_data[slot].cfg_stream_vaddr, + jpeg->slot_data[slot].cfg_stream_handle); + + jpeg->slot_data[slot].used = false; +} + +static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) +{ + struct mxc_jpeg_dev *jpeg = priv; + struct mxc_jpeg_ctx *ctx; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mxc_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state; + u32 dec_ret, com_status; + unsigned long payload; + struct mxc_jpeg_q_data *q_data; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + unsigned int slot; + + spin_lock(&jpeg->hw_lock); + + com_status = readl(reg + COM_STATUS); + slot = COM_STATUS_CUR_SLOT(com_status); + dev_dbg(dev, "Irq %d on slot %d.\n", irq, slot); + + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + if (!ctx) { + dev_err(dev, + "Instance released before the end of transaction.\n"); + /* soft reset only resets internal state, not registers */ + mxc_jpeg_sw_reset(reg); + /* clear all interrupts */ + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + goto job_unlock; + } + + if (slot != ctx->slot) { + /* TODO investigate when adding multi-instance support */ + dev_warn(dev, "IRQ slot %d != context slot %d.\n", + slot, ctx->slot); + goto job_unlock; + } + + dec_ret = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(dec_ret, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); /* w1c */ + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!dst_buf || !src_buf) { + dev_err(dev, "No source or destination buffer.\n"); + goto job_unlock; + } + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + + if (dec_ret & SLOT_STATUS_ENC_CONFIG_ERR) { + u32 ret = readl(reg + CAST_STATUS12); + + dev_err(dev, "Encoder/decoder error, status=0x%08x", ret); + mxc_jpeg_sw_reset(reg); + buf_state = VB2_BUF_STATE_ERROR; + goto buffers_done; + } + + if (!(dec_ret & SLOT_STATUS_FRMDONE)) + goto job_unlock; + + if (jpeg->mode == MXC_JPEG_ENCODE && + ctx->enc_state == MXC_JPEG_ENC_CONF) { + ctx->enc_state = MXC_JPEG_ENCODING; + dev_dbg(dev, "Encoder config finished. Start encoding...\n"); + mxc_jpeg_enc_mode_go(dev, reg); + goto job_unlock; + } + if (jpeg->mode == MXC_JPEG_DECODE && jpeg_src_buf->dht_needed) { + jpeg_src_buf->dht_needed = false; + dev_dbg(dev, "Decoder DHT cfg finished. Start decoding...\n"); + goto job_unlock; + } + if (jpeg->mode == MXC_JPEG_ENCODE) { + payload = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_BUF_PTR)); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + dev_dbg(dev, "Encoding finished, payload size: %ld\n", + payload); + } else { + q_data = mxc_jpeg_get_q_data(ctx, cap_type); + payload = q_data->sizeimage[0]; + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); + if (q_data->fmt->colplanes == 2) { + payload = q_data->sizeimage[1]; + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); + } + dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", + vb2_get_plane_payload(&dst_buf->vb2_buf, 0), + vb2_get_plane_payload(&dst_buf->vb2_buf, 1)); + } + + /* short preview of the results */ + dev_dbg(dev, "src_buf preview: "); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 32); + dev_dbg(dev, "dst_buf preview: "); + print_mxc_buf(jpeg, &dst_buf->vb2_buf, 32); + buf_state = VB2_BUF_STATE_DONE; + +buffers_done: + jpeg->slot_data[slot].used = false; /* unused, but don't free */ + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + spin_unlock(&jpeg->hw_lock); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return IRQ_HANDLED; +job_unlock: + spin_unlock(&jpeg->hw_lock); + return IRQ_HANDLED; +} + +static int mxc_jpeg_fixup_sof(struct mxc_jpeg_sof *sof, + u32 fourcc, + u16 w, u16 h) +{ + int sof_length; + + sof->precision = 8; /* TODO allow 8/12 bit precision*/ + sof->height = h; + _bswap16(&sof->height); + sof->width = w; + _bswap16(&sof->width); + + switch (fourcc) { + case V4L2_PIX_FMT_NV12M: + sof->components_no = 3; + sof->comp[0].v = 0x2; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUYV: + sof->components_no = 3; + sof->comp[0].v = 0x1; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_RGB24: + default: + sof->components_no = 3; + break; + case V4L2_PIX_FMT_ARGB32: + sof->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + sof->components_no = 1; + break; + } + sof_length = 8 + 3 * sof->components_no; + sof->length = sof_length; + _bswap16(&sof->length); + + return sof_length; /* not swaped */ +} + +static int mxc_jpeg_fixup_sos(struct mxc_jpeg_sos *sos, + u32 fourcc) +{ + int sos_length; + u8 *sof_u8 = (u8 *)sos; + + switch (fourcc) { + case V4L2_PIX_FMT_NV12M: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUYV: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_RGB24: + default: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_ARGB32: + sos->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + sos->components_no = 1; + break; + } + sos_length = 6 + 2 * sos->components_no; + sos->length = sos_length; + _bswap16(&sos->length); + + /* SOS ignorable bytes, not so ignorable after all */ + sof_u8[sos_length - 1] = 0x0; + sof_u8[sos_length - 2] = 0x3f; + sof_u8[sos_length - 3] = 0x0; + + return sos_length; /* not swaped */ +} + +static unsigned int mxc_jpeg_setup_cfg_stream(void *cfg_stream_vaddr, + u32 fourcc, + u16 w, u16 h) +{ + unsigned int offset = 0; + u8 *cfg = (u8 *)cfg_stream_vaddr; + struct mxc_jpeg_sof *sof; + struct mxc_jpeg_sos *sos; + + memcpy(cfg + offset, jpeg_soi, ARRAY_SIZE(jpeg_soi)); + offset += ARRAY_SIZE(jpeg_soi); + + if (fourcc == V4L2_PIX_FMT_RGB24 || + fourcc == V4L2_PIX_FMT_ARGB32) { + memcpy(cfg + offset, jpeg_app14, sizeof(jpeg_app14)); + offset += sizeof(jpeg_app14); + } else { + memcpy(cfg + offset, jpeg_app0, sizeof(jpeg_app0)); + offset += sizeof(jpeg_app0); + } + + memcpy(cfg + offset, jpeg_dqt, sizeof(jpeg_dqt)); + offset += sizeof(jpeg_dqt); + + memcpy(cfg + offset, jpeg_sof_maximal, sizeof(jpeg_sof_maximal)); + offset += 2; /* skip marker ID */ + sof = (struct mxc_jpeg_sof *)(cfg + offset); + offset += mxc_jpeg_fixup_sof(sof, fourcc, w, h); + + memcpy(cfg + offset, jpeg_dht, sizeof(jpeg_dht)); + offset += sizeof(jpeg_dht); + + memcpy(cfg + offset, jpeg_dri, sizeof(jpeg_dri)); + offset += sizeof(jpeg_dri); + + memcpy(cfg + offset, jpeg_sos_maximal, sizeof(jpeg_sos_maximal)); + offset += 2; /* skip marker ID */ + sos = (struct mxc_jpeg_sos *)(cfg + offset); + offset += mxc_jpeg_fixup_sos(sos, fourcc); + + memcpy(cfg + offset, jpeg_image_red, sizeof(jpeg_image_red)); + offset += sizeof(jpeg_image_red); + + memcpy(cfg + offset, jpeg_eoi, sizeof(jpeg_eoi)); + offset += sizeof(jpeg_eoi); + + return offset; +} + +static void mxc_jpeg_config_dec_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + struct mxc_jpeg_q_data *q_data_cap; + enum mxc_jpeg_image_format img_fmt; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data[slot].desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data[slot].cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data[slot].desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data[slot].cfg_desc_handle; + dma_addr_t cfg_stream_handle = jpeg->slot_data[slot].cfg_stream_handle; + unsigned int *cfg_size = &jpeg->slot_data[slot].cfg_stream_size; + void *cfg_stream_vaddr = jpeg->slot_data[slot].cfg_stream_vaddr; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + jpeg_src_buf = vb2_to_mxc_buf(src_buf); + + /* setup the decoding descriptor */ + desc->next_descpt_ptr = 0; /* end of chain */ + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + desc->imgsize = q_data_cap->w_adjusted << 16 | q_data_cap->h_adjusted; + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data_cap->fmt->fourcc); + desc->stm_ctrl &= ~STM_CTRL_IMAGE_FORMAT(0xF); /* clear image format */ + desc->stm_ctrl |= STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + desc->line_pitch = q_data_cap->bytesperline[0]; + mxc_jpeg_addrs(desc, dst_buf, src_buf, 0); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(src_buf, 0), 1024)); + print_descriptor_info(jpeg->dev, desc); + + if (!jpeg_src_buf->dht_needed) { + /* validate the decoding descriptor */ + mxc_jpeg_set_desc(desc_handle, reg, slot); + return; + } + + /* + * if a default huffman table is needed, use the config descriptor to + * inject a DHT, by chaining it before the decoding descriptor + */ + *cfg_size = mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + V4L2_PIX_FMT_YUYV, + MXC_JPEG_MIN_WIDTH, + MXC_JPEG_MIN_HEIGHT); + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + cfg_desc->buf_base0 = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + cfg_desc->buf_base1 = 0; + cfg_desc->imgsize = MXC_JPEG_MIN_WIDTH << 16; + cfg_desc->imgsize |= MXC_JPEG_MIN_HEIGHT; + cfg_desc->line_pitch = MXC_JPEG_MIN_WIDTH * 2; + cfg_desc->stm_ctrl = STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV422); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + cfg_desc->stm_bufbase = cfg_stream_handle; + cfg_desc->stm_bufsize = ALIGN(*cfg_size, 1024); + print_descriptor_info(jpeg->dev, cfg_desc); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data[slot].desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data[slot].cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data[slot].desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data[slot].cfg_desc_handle; + void *cfg_stream_vaddr = jpeg->slot_data[slot].cfg_stream_vaddr; + struct mxc_jpeg_q_data *q_data; + enum mxc_jpeg_image_format img_fmt; + int w, h; + + q_data = mxc_jpeg_get_q_data(ctx, src_buf->vb2_queue->type); + + jpeg->slot_data[slot].cfg_stream_size = + mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + q_data->fmt->fourcc, + q_data->w_adjusted, + q_data->h_adjusted); + + /* chain the config descriptor with the encoding descriptor */ + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + + cfg_desc->buf_base0 = jpeg->slot_data[slot].cfg_stream_handle; + cfg_desc->buf_base1 = 0; + cfg_desc->line_pitch = 0; + cfg_desc->stm_bufbase = 0; /* no output expected */ + cfg_desc->stm_bufsize = 0x0; + cfg_desc->imgsize = 0; + cfg_desc->stm_ctrl = STM_CTRL_CONFIG_MOD(1); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + + desc->next_descpt_ptr = 0; /* end of chain */ + + /* use adjusted resolution for CAST IP job */ + w = q_data->w_adjusted; + h = q_data->h_adjusted; + mxc_jpeg_set_res(desc, w, h); + mxc_jpeg_set_line_pitch(desc, w * (q_data->fmt->depth / 8)); + mxc_jpeg_set_bufsize(desc, desc->line_pitch * h); + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data->fmt->fourcc); + if (img_fmt == MXC_JPEG_INVALID) + dev_err(jpeg->dev, "No valid image format detected\n"); + desc->stm_ctrl = STM_CTRL_CONFIG_MOD(0) | + STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + mxc_jpeg_addrs(desc, src_buf, dst_buf, 0); + dev_dbg(jpeg->dev, "cfg_desc:\n"); + print_descriptor_info(jpeg->dev, cfg_desc); + dev_dbg(jpeg->dev, "enc desc:\n"); + print_descriptor_info(jpeg->dev, desc); + print_wrapper_info(jpeg->dev, reg); + print_cast_status(jpeg->dev, reg, MXC_JPEG_ENCODE); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static void mxc_jpeg_device_run(void *priv) +{ + struct mxc_jpeg_ctx *ctx = priv; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long flags; + struct mxc_jpeg_q_data *q_data_cap, *q_data_out; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!src_buf || !dst_buf) { + dev_err(dev, "Null src or dst buf\n"); + goto end; + } + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (!q_data_cap) + goto end; + q_data_out = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (!q_data_out) + goto end; + src_buf->sequence = q_data_out->sequence++; + dst_buf->sequence = q_data_cap->sequence++; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + if (q_data_cap->fmt->colplanes != dst_buf->vb2_buf.num_planes) { + dev_err(dev, "Capture format %s has %d planes, but capture buffer has %d planes\n", + q_data_cap->fmt->name, q_data_cap->fmt->colplanes, + dst_buf->vb2_buf.num_planes); + jpeg_src_buf->jpeg_parse_error = true; + } + if (jpeg_src_buf->jpeg_parse_error) { + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + + return; + } + + mxc_jpeg_enable(reg); + mxc_jpeg_set_l_endian(reg, 1); + + ctx->slot = mxc_get_free_slot(jpeg->slot_data, MXC_MAX_SLOTS); + if (ctx->slot >= MXC_MAX_SLOTS) { + dev_err(dev, "No more free slots\n"); + goto end; + } + if (!mxc_jpeg_alloc_slot_data(jpeg, ctx->slot)) { + dev_err(dev, "Cannot allocate slot data\n"); + goto end; + } + + mxc_jpeg_enable_slot(reg, ctx->slot); + mxc_jpeg_enable_irq(reg, ctx->slot); + + if (jpeg->mode == MXC_JPEG_ENCODE) { + dev_dbg(dev, "Encoding on slot %d\n", ctx->slot); + ctx->enc_state = MXC_JPEG_ENC_CONF; + mxc_jpeg_config_enc_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + mxc_jpeg_enc_mode_conf(dev, reg); /* start config phase */ + } else { + dev_dbg(dev, "Decoding on slot %d\n", ctx->slot); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 0); + mxc_jpeg_config_dec_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + mxc_jpeg_dec_mode_go(dev, reg); + } +end: + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + +static void mxc_jpeg_set_last_buffer_dequeued(struct mxc_jpeg_ctx *ctx) +{ + struct vb2_queue *q; + + ctx->stopped = 1; + q = v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx); + if (!list_empty(&q->done_list)) + return; + + q->last_buffer_dequeued = true; + wake_up(&q->done_wq); + ctx->stopped = 0; +} + +static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct device *dev = ctx->mxc_jpeg->dev; + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (cmd->cmd == V4L2_DEC_CMD_STOP) { + dev_dbg(dev, "Received V4L2_DEC_CMD_STOP"); + if (v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx) == 0) { + /* No more src bufs, notify app EOS */ + notify_eos(ctx); + mxc_jpeg_set_last_buffer_dequeued(ctx); + } else { + /* will send EOS later*/ + ctx->stopping = 1; + } + } + + return 0; +} + +static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + struct device *dev = ctx->mxc_jpeg->dev; + int ret; + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (cmd->cmd == V4L2_ENC_CMD_STOP) { + dev_dbg(dev, "Received V4L2_ENC_CMD_STOP"); + if (v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx) == 0) { + /* No more src bufs, notify app EOS */ + notify_eos(ctx); + mxc_jpeg_set_last_buffer_dequeued(ctx); + } else { + /* will send EOS later*/ + ctx->stopping = 1; + } + } + + return 0; +} + +static int mxc_jpeg_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = NULL; + int i; + + q_data = mxc_jpeg_get_q_data(ctx, q->type); + if (!q_data) + return -EINVAL; + + /* Handle CREATE_BUFS situation - *nplanes != 0 */ + if (*nplanes) { + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < q_data->sizeimage[i]) + return -EINVAL; + } + return 0; + } + + /* Handle REQBUFS situation */ + *nplanes = q_data->fmt->colplanes; + for (i = 0; i < *nplanes; i++) + sizes[i] = q_data->sizeimage[i]; + + return 0; +} + +static int mxc_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, q->type); + int ret; + + dev_dbg(ctx->mxc_jpeg->dev, "Start streaming ctx=%p", ctx); + q_data->sequence = 0; + + ret = pm_runtime_resume_and_get(ctx->mxc_jpeg->dev); + if (ret < 0) { + dev_err(ctx->mxc_jpeg->dev, "Failed to power up jpeg\n"); + return ret; + } + + return 0; +} + +static void mxc_jpeg_stop_streaming(struct vb2_queue *q) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + dev_dbg(ctx->mxc_jpeg->dev, "Stop streaming ctx=%p", ctx); + + /* Release all active buffers */ + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + break; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } + pm_runtime_put_sync(&ctx->mxc_jpeg->pdev->dev); + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->stopping = 0; + ctx->stopped = 0; + } +} + +static int mxc_jpeg_valid_comp_id(struct device *dev, + struct mxc_jpeg_sof *sof, + struct mxc_jpeg_sos *sos) +{ + int valid = 1; + int i; + + /* + * there's a limitation in the IP that the component IDs must be + * between 0..4, if they are not, let's patch them + */ + for (i = 0; i < sof->components_no; i++) + if (sof->comp[i].id > MXC_JPEG_MAX_COMPONENTS) { + valid = 0; + dev_err(dev, "Component %d has invalid ID: %d", + i, sof->comp[i].id); + } + if (!valid) + /* patch all comp IDs if at least one is invalid */ + for (i = 0; i < sof->components_no; i++) { + dev_warn(dev, "Component %d ID patched to: %d", + i, i + 1); + sof->comp[i].id = i + 1; + sos->comp[i].id = i + 1; + } + + return valid; +} + +static u32 mxc_jpeg_get_image_format(struct device *dev, + const struct v4l2_jpeg_header *header) +{ + int i; + u32 fourcc = 0; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) + if (mxc_formats[i].subsampling == header->frame.subsampling && + mxc_formats[i].nc == header->frame.num_components) { + fourcc = mxc_formats[i].fourcc; + break; + } + if (fourcc == 0) { + dev_err(dev, "Could not identify image format nc=%d, subsampling=%d\n", + header->frame.num_components, + header->frame.subsampling); + return fourcc; + } + /* + * If the transform flag from APP14 marker is 0, images that are + * encoded with 3 components have RGB colorspace, see Recommendation + * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding + */ + if (fourcc == V4L2_PIX_FMT_YUV24 || fourcc == V4L2_PIX_FMT_RGB24) { + if (header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB) + fourcc = V4L2_PIX_FMT_RGB24; + else + fourcc = V4L2_PIX_FMT_YUV24; + } + + return fourcc; +} + +static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, + u32 precision) +{ + /* Bytes distance between the leftmost pixels in two adjacent lines */ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* bytesperline unused for compressed formats */ + q->bytesperline[0] = 0; + q->bytesperline[1] = 0; + } else if (q->fmt->fourcc == V4L2_PIX_FMT_NV12M) { + /* When the image format is planar the bytesperline value + * applies to the first plane and is divided by the same factor + * as the width field for the other planes + */ + q->bytesperline[0] = q->w * (precision / 8) * + (q->fmt->depth / 8); + q->bytesperline[1] = q->bytesperline[0]; + } else { + /* single plane formats */ + q->bytesperline[0] = q->w * (precision / 8) * + (q->fmt->depth / 8); + q->bytesperline[1] = 0; + } +} + +static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q) +{ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* if no sizeimage from user, assume worst jpeg compression */ + if (!q->sizeimage[0]) + q->sizeimage[0] = 6 * q->w * q->h; + q->sizeimage[1] = 0; + + if (q->sizeimage[0] > MXC_JPEG_MAX_SIZEIMAGE) + q->sizeimage[0] = MXC_JPEG_MAX_SIZEIMAGE; + + /* jpeg stream size must be multiple of 1K */ + q->sizeimage[0] = ALIGN(q->sizeimage[0], 1024); + } else { + q->sizeimage[0] = q->bytesperline[0] * q->h; + q->sizeimage[1] = 0; + if (q->fmt->fourcc == V4L2_PIX_FMT_NV12M) + q->sizeimage[1] = q->sizeimage[0] / 2; + } +} + +static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, + u8 *src_addr, u32 size, bool *dht_needed) +{ + struct device *dev = ctx->mxc_jpeg->dev; + struct mxc_jpeg_q_data *q_data_out, *q_data_cap; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + bool src_chg = false; + u32 fourcc; + struct v4l2_jpeg_header header; + struct mxc_jpeg_sof *psof = NULL; + struct mxc_jpeg_sos *psos = NULL; + int ret; + + memset(&header, 0, sizeof(header)); + ret = v4l2_jpeg_parse_header((void *)src_addr, size, &header); + if (ret < 0) { + dev_err(dev, "Error parsing JPEG stream markers\n"); + return ret; + } + + /* if DHT marker present, no need to inject default one */ + *dht_needed = (header.num_dht == 0); + + q_data_out = mxc_jpeg_get_q_data(ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (q_data_out->w == 0 && q_data_out->h == 0) { + dev_warn(dev, "Invalid user resolution 0x0"); + dev_warn(dev, "Keeping resolution from JPEG: %dx%d", + header.frame.width, header.frame.height); + q_data_out->w = header.frame.width; + q_data_out->h = header.frame.height; + } else if (header.frame.width != q_data_out->w || + header.frame.height != q_data_out->h) { + dev_err(dev, + "Resolution mismatch: %dx%d (JPEG) versus %dx%d(user)", + header.frame.width, header.frame.height, + q_data_out->w, q_data_out->h); + return -EINVAL; + } + if (header.frame.width % 8 != 0 || header.frame.height % 8 != 0) { + dev_err(dev, "JPEG width or height not multiple of 8: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.width > MXC_JPEG_MAX_WIDTH || + header.frame.height > MXC_JPEG_MAX_HEIGHT) { + dev_err(dev, "JPEG width or height should be <= 8192: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.width < MXC_JPEG_MIN_WIDTH || + header.frame.height < MXC_JPEG_MIN_HEIGHT) { + dev_err(dev, "JPEG width or height should be > 64: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.num_components > V4L2_JPEG_MAX_COMPONENTS) { + dev_err(dev, "JPEG number of components should be <=%d", + V4L2_JPEG_MAX_COMPONENTS); + return -EINVAL; + } + /* check and, if necessary, patch component IDs*/ + psof = (struct mxc_jpeg_sof *)header.sof.start; + psos = (struct mxc_jpeg_sos *)header.sos.start; + if (!mxc_jpeg_valid_comp_id(dev, psof, psos)) + dev_warn(dev, "JPEG component ids should be 0-3 or 1-4"); + + fourcc = mxc_jpeg_get_image_format(dev, &header); + if (fourcc == 0) + return -EINVAL; + + /* + * set-up the capture queue with the pixelformat and resolution + * detected from the jpeg output stream + */ + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + if (q_data_cap->w != header.frame.width || + q_data_cap->h != header.frame.height) + src_chg = true; + q_data_cap->w = header.frame.width; + q_data_cap->h = header.frame.height; + q_data_cap->fmt = mxc_jpeg_find_format(ctx, fourcc); + q_data_cap->w_adjusted = q_data_cap->w; + q_data_cap->h_adjusted = q_data_cap->h; + /* + * align up the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data_cap->w_adjusted, + q_data_cap->w_adjusted, /* adjust up */ + MXC_JPEG_MAX_WIDTH, + q_data_cap->fmt->h_align, + &q_data_cap->h_adjusted, + q_data_cap->h_adjusted, /* adjust up */ + MXC_JPEG_MAX_HEIGHT, + q_data_cap->fmt->v_align, + 0); + dev_dbg(dev, "Detected jpeg res=(%dx%d)->(%dx%d), pixfmt=%c%c%c%c\n", + q_data_cap->w, q_data_cap->h, + q_data_cap->w_adjusted, q_data_cap->h_adjusted, + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + + /* setup bytesperline/sizeimage for capture queue */ + mxc_jpeg_bytesperline(q_data_cap, header.frame.precision); + mxc_jpeg_sizeimage(q_data_cap); + + /* + * if the CAPTURE format was updated with new values, regardless of + * whether they match the values set by the client or not, signal + * a source change event + */ + if (src_chg) + notify_src_chg(ctx); + + return 0; +} + +static void mxc_jpeg_buf_queue(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_src_buf *jpeg_src_buf; + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + goto end; + + /* for V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE */ + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + goto end; + + jpeg_src_buf = vb2_to_mxc_buf(vb); + jpeg_src_buf->jpeg_parse_error = false; + ret = mxc_jpeg_parse(ctx, + (u8 *)vb2_plane_vaddr(vb, 0), + vb2_get_plane_payload(vb, 0), + &jpeg_src_buf->dht_needed); + if (ret) + jpeg_src_buf->jpeg_parse_error = true; + +end: + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int mxc_jpeg_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_q_data *q_data = NULL; + struct device *dev = ctx->mxc_jpeg->dev; + unsigned long sizeimage; + int i; + + vbuf->field = V4L2_FIELD_NONE; + + q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); + if (!q_data) + return -EINVAL; + for (i = 0; i < q_data->fmt->colplanes; i++) { + sizeimage = q_data->sizeimage[i]; + if (vb2_plane_size(vb, i) < sizeimage) { + dev_err(dev, "plane %d too small (%lu < %lu)", + i, vb2_plane_size(vb, i), sizeimage); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, sizeimage); + } + return 0; +} + +static void mxc_jpeg_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_queue *q = vb->vb2_queue; + + if (V4L2_TYPE_IS_OUTPUT(vb->type)) + return; + if (!ctx->stopped) + return; + if (list_empty(&q->done_list)) { + vbuf->flags |= V4L2_BUF_FLAG_LAST; + ctx->stopped = 0; + } +} + +static const struct vb2_ops mxc_jpeg_qops = { + .queue_setup = mxc_jpeg_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_out_validate = mxc_jpeg_buf_out_validate, + .buf_prepare = mxc_jpeg_buf_prepare, + .buf_finish = mxc_jpeg_buf_finish, + .start_streaming = mxc_jpeg_start_streaming, + .stop_streaming = mxc_jpeg_stop_streaming, + .buf_queue = mxc_jpeg_buf_queue, +}; + +static int mxc_jpeg_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mxc_jpeg_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct mxc_jpeg_src_buf); + src_vq->ops = &mxc_jpeg_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->mxc_jpeg->lock; + src_vq->dev = ctx->mxc_jpeg->dev; + src_vq->allow_zero_bytesused = 1; /* keep old userspace apps working */ + + 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_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &mxc_jpeg_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->mxc_jpeg->lock; + dst_vq->dev = ctx->mxc_jpeg->dev; + + ret = vb2_queue_init(dst_vq); + return ret; +} + +static void mxc_jpeg_set_default_params(struct mxc_jpeg_ctx *ctx) +{ + struct mxc_jpeg_q_data *out_q = &ctx->out_q; + struct mxc_jpeg_q_data *cap_q = &ctx->cap_q; + struct mxc_jpeg_q_data *q[2] = {out_q, cap_q}; + int i; + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) { + out_q->fmt = mxc_jpeg_find_format(ctx, MXC_JPEG_DEFAULT_PFMT); + cap_q->fmt = mxc_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG); + } else { + out_q->fmt = mxc_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG); + cap_q->fmt = mxc_jpeg_find_format(ctx, MXC_JPEG_DEFAULT_PFMT); + } + + for (i = 0; i < 2; i++) { + q[i]->w = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->w_adjusted = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h_adjusted = MXC_JPEG_DEFAULT_HEIGHT; + mxc_jpeg_bytesperline(q[i], 8); + mxc_jpeg_sizeimage(q[i]); + } +} + +static int mxc_jpeg_open(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct video_device *mxc_vfd = video_devdata(file); + struct device *dev = mxc_jpeg->dev; + struct mxc_jpeg_ctx *ctx; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&mxc_jpeg->lock)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, mxc_vfd); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->mxc_jpeg = mxc_jpeg; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(mxc_jpeg->m2m_dev, ctx, + mxc_jpeg_queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error; + } + + mxc_jpeg_set_default_params(ctx); + ctx->slot = MXC_MAX_SLOTS; /* slot not allocated yet */ + + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Opened JPEG decoder instance %p\n", ctx); + else + dev_dbg(dev, "Opened JPEG encoder instance %p\n", ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; + +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&mxc_jpeg->lock); +free: + kfree(ctx); + return ret; +} + +static int mxc_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + + strscpy(cap->driver, MXC_JPEG_NAME " codec", sizeof(cap->driver)); + strscpy(cap->card, MXC_JPEG_NAME " codec", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(mxc_jpeg->dev)); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_ENC); + else + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_RAW); +} + +static int mxc_jpeg_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_ENC); + else + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_RAW); +} + +static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fmt, + struct mxc_jpeg_ctx *ctx, int q_type) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt; + u32 w = (pix_mp->width < MXC_JPEG_MAX_WIDTH) ? + pix_mp->width : MXC_JPEG_MAX_WIDTH; + u32 h = (pix_mp->height < MXC_JPEG_MAX_HEIGHT) ? + pix_mp->height : MXC_JPEG_MAX_HEIGHT; + int i; + struct mxc_jpeg_q_data tmp_q; + + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = fmt->colplanes; + pix_mp->pixelformat = fmt->fourcc; + + /* + * use MXC_JPEG_H_ALIGN instead of fmt->v_align, for vertical + * alignment, to loosen up the alignment to multiple of 8, + * otherwise NV12-1080p fails as 1080 is not a multiple of 16 + */ + v4l_bound_align_image(&w, + MXC_JPEG_MIN_WIDTH, + w, /* adjust downwards*/ + fmt->h_align, + &h, + MXC_JPEG_MIN_HEIGHT, + h, /* adjust downwards*/ + MXC_JPEG_H_ALIGN, + 0); + pix_mp->width = w; /* negotiate the width */ + pix_mp->height = h; /* negotiate the height */ + + /* get user input into the tmp_q */ + tmp_q.w = w; + tmp_q.h = h; + tmp_q.fmt = fmt; + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + tmp_q.bytesperline[i] = pfmt->bytesperline; + tmp_q.sizeimage[i] = pfmt->sizeimage; + } + + /* calculate bytesperline & sizeimage into the tmp_q */ + mxc_jpeg_bytesperline(&tmp_q, 8); + mxc_jpeg_sizeimage(&tmp_q); + + /* adjust user format according to our calculations */ + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + memset(pfmt->reserved, 0, sizeof(pfmt->reserved)); + pfmt->bytesperline = tmp_q.bytesperline[i]; + pfmt->sizeimage = tmp_q.sizeimage[i]; + } + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + /* + * this hardware does not change the range of the samples + * but since inside JPEG the YUV quantization is full-range, + * this driver will always use full-range for the raw frames, too + */ + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + return 0; +} + +static int mxc_jpeg_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + const struct mxc_jpeg_fmt *fmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; + + int q_type = (jpeg->mode == MXC_JPEG_DECODE) ? + MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != q_type) { + dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_DECODE) ? + MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; + fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); + } + return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); +} + +static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + const struct mxc_jpeg_fmt *fmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; + + int q_type = (jpeg->mode == MXC_JPEG_ENCODE) ? + MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != q_type) { + dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_ENCODE) ? + MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; + fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); + } + return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); +} + +static int mxc_jpeg_s_fmt(struct mxc_jpeg_ctx *ctx, + struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct mxc_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + int i; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = mxc_jpeg_get_q_data(ctx, f->type); + + if (vb2_is_busy(vq)) { + v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); + return -EBUSY; + } + + q_data->fmt = mxc_jpeg_find_format(ctx, pix_mp->pixelformat); + q_data->w = pix_mp->width; + q_data->h = pix_mp->height; + + q_data->w_adjusted = q_data->w; + q_data->h_adjusted = q_data->h; + if (jpeg->mode == MXC_JPEG_DECODE) { + /* + * align up the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data->w_adjusted, + q_data->w_adjusted, /* adjust upwards */ + MXC_JPEG_MAX_WIDTH, + q_data->fmt->h_align, + &q_data->h_adjusted, + q_data->h_adjusted, /* adjust upwards */ + MXC_JPEG_MAX_HEIGHT, + q_data->fmt->v_align, + 0); + } else { + /* + * align down the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data->w_adjusted, + MXC_JPEG_MIN_WIDTH, + q_data->w_adjusted, /* adjust downwards*/ + q_data->fmt->h_align, + &q_data->h_adjusted, + MXC_JPEG_MIN_HEIGHT, + q_data->h_adjusted, /* adjust downwards*/ + q_data->fmt->v_align, + 0); + } + + for (i = 0; i < pix_mp->num_planes; i++) { + q_data->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; + q_data->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; + } + + return 0; +} + +static int mxc_jpeg_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = mxc_jpeg_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); +} + +static int mxc_jpeg_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = mxc_jpeg_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); +} + +static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, f->type); + int i; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "G_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->width = q_data->w; + pix_mp->height = q_data->h; + pix_mp->field = V4L2_FIELD_NONE; + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + pix_mp->num_planes = q_data->fmt->colplanes; + for (i = 0; i < pix_mp->num_planes; i++) { + pix_mp->plane_fmt[i].bytesperline = q_data->bytesperline[i]; + pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; + } + + return 0; +} + +static int mxc_jpeg_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return -EINVAL; + } +} + +static int mxc_jpeg_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct device *dev = ctx->mxc_jpeg->dev; + int num_src_ready = v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx); + int ret; + + dev_dbg(dev, "DQBUF type=%d, index=%d", buf->type, buf->index); + if (ctx->stopping == 1 && num_src_ready == 0) { + /* No more src bufs, notify app EOS */ + notify_eos(ctx); + ctx->stopping = 0; + mxc_jpeg_set_last_buffer_dequeued(ctx); + } + + ret = v4l2_m2m_dqbuf(file, fh->m2m_ctx, buf); + return ret; +} + +static const struct v4l2_ioctl_ops mxc_jpeg_ioctl_ops = { + .vidioc_querycap = mxc_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = mxc_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mxc_jpeg_enum_fmt_vid_out, + + .vidioc_try_fmt_vid_cap_mplane = mxc_jpeg_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out_mplane = mxc_jpeg_try_fmt_vid_out, + + .vidioc_s_fmt_vid_cap_mplane = mxc_jpeg_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out_mplane = mxc_jpeg_s_fmt_vid_out, + + .vidioc_g_fmt_vid_cap_mplane = mxc_jpeg_g_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = mxc_jpeg_g_fmt_vid, + + .vidioc_subscribe_event = mxc_jpeg_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = mxc_jpeg_decoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = mxc_jpeg_encoder_cmd, + + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = mxc_jpeg_dqbuf, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static int mxc_jpeg_release(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(file->private_data); + struct device *dev = mxc_jpeg->dev; + + mutex_lock(&mxc_jpeg->lock); + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Release JPEG decoder instance on slot %d.", + ctx->slot); + else + dev_dbg(dev, "Release JPEG encoder instance on slot %d.", + ctx->slot); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; +} + +static const struct v4l2_file_operations mxc_jpeg_fops = { + .owner = THIS_MODULE, + .open = mxc_jpeg_open, + .release = mxc_jpeg_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_m2m_ops mxc_jpeg_m2m_ops = { + .device_run = mxc_jpeg_device_run, +}; + +static void mxc_jpeg_detach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + int i; + + for (i = 0; i < jpeg->num_domains; i++) { + if (jpeg->pd_link[i] && !IS_ERR(jpeg->pd_link[i])) + device_link_del(jpeg->pd_link[i]); + if (jpeg->pd_dev[i] && !IS_ERR(jpeg->pd_dev[i])) + dev_pm_domain_detach(jpeg->pd_dev[i], true); + jpeg->pd_dev[i] = NULL; + jpeg->pd_link[i] = NULL; + } +} + +static int mxc_jpeg_attach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + struct device *dev = jpeg->dev; + struct device_node *np = jpeg->pdev->dev.of_node; + int i; + int ret; + + jpeg->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + if (jpeg->num_domains < 0) { + dev_err(dev, "No power domains defined for jpeg node\n"); + return jpeg->num_domains; + } + + jpeg->pd_dev = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_dev), GFP_KERNEL); + if (!jpeg->pd_dev) + return -ENOMEM; + + jpeg->pd_link = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_link), GFP_KERNEL); + if (!jpeg->pd_link) + return -ENOMEM; + + for (i = 0; i < jpeg->num_domains; i++) { + jpeg->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(jpeg->pd_dev[i])) { + ret = PTR_ERR(jpeg->pd_dev[i]); + goto fail; + } + + jpeg->pd_link[i] = device_link_add(dev, jpeg->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME); + if (!jpeg->pd_link[i]) { + ret = -EINVAL; + goto fail; + } + } + + return 0; +fail: + mxc_jpeg_detach_pm_domains(jpeg); + return ret; +} + +static int mxc_jpeg_probe(struct platform_device *pdev) +{ + struct mxc_jpeg_dev *jpeg; + struct device *dev = &pdev->dev; + int dec_irq; + int ret; + int mode; + const struct of_device_id *of_id; + unsigned int slot; + + of_id = of_match_node(mxc_jpeg_match, dev->of_node); + mode = *(const int *)of_id->data; + + jpeg = devm_kzalloc(dev, sizeof(struct mxc_jpeg_dev), GFP_KERNEL); + if (!jpeg) + return -ENOMEM; + + mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->hw_lock); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available.\n"); + goto err_irq; + } + + jpeg->base_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->base_reg)) + return PTR_ERR(jpeg->base_reg); + + for (slot = 0; slot < MXC_MAX_SLOTS; slot++) { + dec_irq = platform_get_irq(pdev, slot); + if (dec_irq < 0) { + ret = dec_irq; + goto err_irq; + } + ret = devm_request_irq(&pdev->dev, dec_irq, mxc_jpeg_dec_irq, + 0, pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d (%d)\n", + dec_irq, ret); + goto err_irq; + } + } + + jpeg->pdev = pdev; + jpeg->dev = dev; + jpeg->mode = mode; + + /* Get clocks */ + jpeg->clk_ipg = devm_clk_get(dev, "ipg"); + if (IS_ERR(jpeg->clk_ipg)) { + dev_err(dev, "failed to get clock: ipg\n"); + goto err_clk; + } + + jpeg->clk_per = devm_clk_get(dev, "per"); + if (IS_ERR(jpeg->clk_per)) { + dev_err(dev, "failed to get clock: per\n"); + goto err_clk; + } + + ret = mxc_jpeg_attach_pm_domains(jpeg); + if (ret < 0) { + dev_err(dev, "failed to attach power domains %d\n", ret); + return ret; + } + + /* v4l2 */ + ret = v4l2_device_register(dev, &jpeg->v4l2_dev); + if (ret) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_register; + } + jpeg->m2m_dev = v4l2_m2m_init(&mxc_jpeg_m2m_ops); + if (IS_ERR(jpeg->m2m_dev)) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = PTR_ERR(jpeg->m2m_dev); + goto err_m2m; + } + + jpeg->dec_vdev = video_device_alloc(); + if (!jpeg->dec_vdev) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = -ENOMEM; + goto err_vdev_alloc; + } + if (mode == MXC_JPEG_ENCODE) + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-enc", MXC_JPEG_NAME); + else + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-dec", MXC_JPEG_NAME); + + jpeg->dec_vdev->fops = &mxc_jpeg_fops; + jpeg->dec_vdev->ioctl_ops = &mxc_jpeg_ioctl_ops; + jpeg->dec_vdev->minor = -1; + jpeg->dec_vdev->release = video_device_release; + jpeg->dec_vdev->lock = &jpeg->lock; /* lock for ioctl serialization */ + jpeg->dec_vdev->v4l2_dev = &jpeg->v4l2_dev; + jpeg->dec_vdev->vfl_dir = VFL_DIR_M2M; + jpeg->dec_vdev->device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + if (mode == MXC_JPEG_ENCODE) { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_DECODER_CMD); + } else { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_ENCODER_CMD); + } + ret = video_register_device(jpeg->dec_vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "failed to register video device\n"); + goto err_vdev_register; + } + video_set_drvdata(jpeg->dec_vdev, jpeg); + if (mode == MXC_JPEG_ENCODE) + v4l2_info(&jpeg->v4l2_dev, + "encoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + else + v4l2_info(&jpeg->v4l2_dev, + "decoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + + platform_set_drvdata(pdev, jpeg); + pm_runtime_enable(dev); + + return 0; + +err_vdev_register: + video_device_release(jpeg->dec_vdev); + +err_vdev_alloc: + v4l2_m2m_release(jpeg->m2m_dev); + +err_m2m: + v4l2_device_unregister(&jpeg->v4l2_dev); + +err_register: + mxc_jpeg_detach_pm_domains(jpeg); + +err_irq: +err_clk: + return ret; +} + +#ifdef CONFIG_PM +static int mxc_jpeg_runtime_resume(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(jpeg->clk_ipg); + if (ret < 0) { + dev_err(dev, "failed to enable clock: ipg\n"); + goto err_ipg; + } + + ret = clk_prepare_enable(jpeg->clk_per); + if (ret < 0) { + dev_err(dev, "failed to enable clock: per\n"); + goto err_per; + } + + return 0; + +err_per: + clk_disable_unprepare(jpeg->clk_ipg); +err_ipg: + return ret; +} + +static int mxc_jpeg_runtime_suspend(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + + clk_disable_unprepare(jpeg->clk_ipg); + clk_disable_unprepare(jpeg->clk_per); + + return 0; +} +#endif + +static const struct dev_pm_ops mxc_jpeg_pm_ops = { + SET_RUNTIME_PM_OPS(mxc_jpeg_runtime_suspend, + mxc_jpeg_runtime_resume, NULL) +}; + +static int mxc_jpeg_remove(struct platform_device *pdev) +{ + unsigned int slot; + struct mxc_jpeg_dev *jpeg = platform_get_drvdata(pdev); + + for (slot = 0; slot < MXC_MAX_SLOTS; slot++) + mxc_jpeg_free_slot_data(jpeg, slot); + + pm_runtime_disable(&pdev->dev); + video_unregister_device(jpeg->dec_vdev); + v4l2_m2m_release(jpeg->m2m_dev); + v4l2_device_unregister(&jpeg->v4l2_dev); + mxc_jpeg_detach_pm_domains(jpeg); + + return 0; +} + +MODULE_DEVICE_TABLE(of, mxc_jpeg_match); + +static struct platform_driver mxc_jpeg_driver = { + .probe = mxc_jpeg_probe, + .remove = mxc_jpeg_remove, + .driver = { + .name = "mxc-jpeg", + .of_match_table = mxc_jpeg_match, + .pm = &mxc_jpeg_pm_ops, + }, +}; +module_platform_driver(mxc_jpeg_driver); + +MODULE_AUTHOR("Zhengyu Shen <zhengyu.shen_1@nxp.com>"); +MODULE_AUTHOR("Mirela Rabulea <mirela.rabulea@nxp.com>"); +MODULE_DESCRIPTION("V4L2 driver for i.MX8 QXP/QM JPEG encoder/decoder"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h new file mode 100644 index 000000000000..f53f004ba851 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fh.h> + +#ifndef _MXC_JPEG_CORE_H +#define _MXC_JPEG_CORE_H + +#define MXC_JPEG_NAME "mxc-jpeg" +#define MXC_JPEG_FMT_TYPE_ENC 0 +#define MXC_JPEG_FMT_TYPE_RAW 1 +#define MXC_JPEG_DEFAULT_WIDTH 1280 +#define MXC_JPEG_DEFAULT_HEIGHT 720 +#define MXC_JPEG_DEFAULT_PFMT V4L2_PIX_FMT_RGB24 +#define MXC_JPEG_MIN_WIDTH 64 +#define MXC_JPEG_MIN_HEIGHT 64 +#define MXC_JPEG_MAX_WIDTH 0x2000 +#define MXC_JPEG_MAX_HEIGHT 0x2000 +#define MXC_JPEG_MAX_CFG_STREAM 0x1000 +#define MXC_JPEG_H_ALIGN 3 +#define MXC_JPEG_W_ALIGN 3 +#define MXC_JPEG_MAX_SIZEIMAGE 0xFFFFFC00 +#define MXC_JPEG_MAX_PLANES 2 + +enum mxc_jpeg_enc_state { + MXC_JPEG_ENCODING = 0, /* jpeg encode phase */ + MXC_JPEG_ENC_CONF = 1, /* jpeg encoder config phase */ +}; + +enum mxc_jpeg_mode { + MXC_JPEG_DECODE = 0, /* jpeg decode mode */ + MXC_JPEG_ENCODE = 1, /* jpeg encode mode */ +}; + +/** + * struct mxc_jpeg_fmt - driver's internal color format data + * @name: format description + * @fourcc: fourcc code, 0 if not applicable + * @subsampling: subsampling of jpeg components + * @nc: number of color components + * @depth: number of bits per pixel + * @colplanes: number of color planes (1 for packed formats) + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @flags: flags describing format applicability + */ +struct mxc_jpeg_fmt { + const char *name; + u32 fourcc; + enum v4l2_jpeg_chroma_subsampling subsampling; + int nc; + int depth; + int colplanes; + int h_align; + int v_align; + u32 flags; +}; + +struct mxc_jpeg_desc { + u32 next_descpt_ptr; + u32 buf_base0; + u32 buf_base1; + u32 line_pitch; + u32 stm_bufbase; + u32 stm_bufsize; + u32 imgsize; + u32 stm_ctrl; +} __packed; + +struct mxc_jpeg_q_data { + const struct mxc_jpeg_fmt *fmt; + u32 sizeimage[MXC_JPEG_MAX_PLANES]; + u32 bytesperline[MXC_JPEG_MAX_PLANES]; + int w; + int w_adjusted; + int h; + int h_adjusted; + unsigned int sequence; +}; + +struct mxc_jpeg_ctx { + struct mxc_jpeg_dev *mxc_jpeg; + struct mxc_jpeg_q_data out_q; + struct mxc_jpeg_q_data cap_q; + struct v4l2_fh fh; + enum mxc_jpeg_enc_state enc_state; + unsigned int stopping; + unsigned int stopped; + unsigned int slot; +}; + +struct mxc_jpeg_slot_data { + bool used; + struct mxc_jpeg_desc *desc; // enc/dec descriptor + struct mxc_jpeg_desc *cfg_desc; // configuration descriptor + void *cfg_stream_vaddr; // configuration bitstream virtual address + unsigned int cfg_stream_size; + dma_addr_t desc_handle; + dma_addr_t cfg_desc_handle; // configuration descriptor dma address + dma_addr_t cfg_stream_handle; // configuration bitstream dma address +}; + +struct mxc_jpeg_dev { + spinlock_t hw_lock; /* hardware access lock */ + unsigned int mode; + struct mutex lock; /* v4l2 ioctls serialization */ + struct clk *clk_ipg; + struct clk *clk_per; + struct platform_device *pdev; + struct device *dev; + void __iomem *base_reg; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *dec_vdev; + struct mxc_jpeg_slot_data slot_data[MXC_MAX_SLOTS]; + int num_domains; + struct device **pd_dev; + struct device_link **pd_link; +}; + +/** + * struct mxc_jpeg_sof_comp - JPEG Start Of Frame component fields + * @id: component id + * @v: vertical sampling + * @h: horizontal sampling + * @quantization_table_no: id of quantization table + */ +struct mxc_jpeg_sof_comp { + u8 id; + u8 v :4; + u8 h :4; + u8 quantization_table_no; +} __packed; + +#define MXC_JPEG_MAX_COMPONENTS 4 +/** + * struct mxc_jpeg_sof - JPEG Start Of Frame marker fields + * @length: Start of Frame length + * @precision: precision (bits per pixel per color component) + * @height: image height + * @width: image width + * @components_no: number of color components + * @comp: component fields for each color component + */ +struct mxc_jpeg_sof { + u16 length; + u8 precision; + u16 height, width; + u8 components_no; + struct mxc_jpeg_sof_comp comp[MXC_JPEG_MAX_COMPONENTS]; +} __packed; + +/** + * struct mxc_jpeg_sos_comp - JPEG Start Of Scan component fields + * @id: component id + * @huffman_table_no: id of the Huffman table + */ +struct mxc_jpeg_sos_comp { + u8 id; /*component id*/ + u8 huffman_table_no; +} __packed; + +/** + * struct mxc_jpeg_sos - JPEG Start Of Scan marker fields + * @length: Start of Frame length + * @components_no: number of color components + * @comp: SOS component fields for each color component + * @ignorable_bytes: ignorable bytes + */ +struct mxc_jpeg_sos { + u16 length; + u8 components_no; + struct mxc_jpeg_sos_comp comp[MXC_JPEG_MAX_COMPONENTS]; + u8 ignorable_bytes[3]; +} __packed; + +#endif diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c new file mode 100644 index 000000000000..0a72734db55e --- /dev/null +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -0,0 +1,1581 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung CSIS MIPI CSI-2 receiver driver. + * + * The Samsung CSIS IP is a MIPI CSI-2 receiver found in various NXP i.MX7 and + * i.MX8 SoCs. The i.MX7 features version 3.3 of the IP, while i.MX8 features + * version 3.6.3. + * + * Copyright (C) 2019 Linaro Ltd + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +#define CSIS_DRIVER_NAME "imx-mipi-csis" + +#define CSIS_PAD_SINK 0 +#define CSIS_PAD_SOURCE 1 +#define CSIS_PADS_NUM 2 + +#define MIPI_CSIS_DEF_PIX_WIDTH 640 +#define MIPI_CSIS_DEF_PIX_HEIGHT 480 + +/* Register map definition */ + +/* CSIS common control */ +#define MIPI_CSIS_CMN_CTRL 0x04 +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW BIT(16) +#define MIPI_CSIS_CMN_CTRL_INTER_MODE BIT(10) +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL BIT(2) +#define MIPI_CSIS_CMN_CTRL_RESET BIT(1) +#define MIPI_CSIS_CMN_CTRL_ENABLE BIT(0) + +#define MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET 8 +#define MIPI_CSIS_CMN_CTRL_LANE_NR_MASK (3 << 8) + +/* CSIS clock control */ +#define MIPI_CSIS_CLK_CTRL 0x08 +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH3(x) ((x) << 28) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH2(x) ((x) << 24) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH1(x) ((x) << 20) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(x) ((x) << 16) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK (0xf << 4) +#define MIPI_CSIS_CLK_CTRL_WCLK_SRC BIT(0) + +/* CSIS Interrupt mask */ +#define MIPI_CSIS_INT_MSK 0x10 +#define MIPI_CSIS_INT_MSK_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_MSK_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_MSK_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_MSK_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_MSK_FRAME_START BIT(24) +#define MIPI_CSIS_INT_MSK_FRAME_END BIT(20) +#define MIPI_CSIS_INT_MSK_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_MSK_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_MSK_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_MSK_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_MSK_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_MSK_ERR_UNKNOWN BIT(0) + +/* CSIS Interrupt source */ +#define MIPI_CSIS_INT_SRC 0x14 +#define MIPI_CSIS_INT_SRC_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_SRC_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_SRC_EVEN BIT(30) +#define MIPI_CSIS_INT_SRC_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_SRC_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_SRC_ODD (0x3 << 28) +#define MIPI_CSIS_INT_SRC_NON_IMAGE_DATA (0xf << 28) +#define MIPI_CSIS_INT_SRC_FRAME_START BIT(24) +#define MIPI_CSIS_INT_SRC_FRAME_END BIT(20) +#define MIPI_CSIS_INT_SRC_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_SRC_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_SRC_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_SRC_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_SRC_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_SRC_ERR_UNKNOWN BIT(0) +#define MIPI_CSIS_INT_SRC_ERRORS 0xfffff + +/* D-PHY status control */ +#define MIPI_CSIS_DPHY_STATUS 0x20 +#define MIPI_CSIS_DPHY_STATUS_ULPS_DAT BIT(8) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_DAT BIT(4) +#define MIPI_CSIS_DPHY_STATUS_ULPS_CLK BIT(1) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_CLK BIT(0) + +/* D-PHY common control */ +#define MIPI_CSIS_DPHY_CMN_CTRL 0x24 +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(n) ((n) << 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE_MASK GENMASK(31, 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(n) ((n) << 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE_MASK GENMASK(23, 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_CLK BIT(6) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_DAT BIT(5) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_DAT BIT(1) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_CLK BIT(0) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE (0x1f << 0) + +/* D-PHY Master and Slave Control register Low */ +#define MIPI_CSIS_DPHY_BCTRL_L 0x30 +#define MIPI_CSIS_DPHY_BCTRL_L_USER_DATA_PATTERN_LOW(n) (((n) & 3U) << 30) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV (0 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_724MV (1 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_733MV (2 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_706MV (3 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ (0 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_1_5MHZ (1 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_VREG12_EXTPWR_EN_CTL BIT(26) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V (0 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_23V (1 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_17V (2 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_26V (3 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_1P2_LVL_SEL BIT(23) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV (0 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_100MV (1 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_120MV (2 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_140MV (3 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_VREF_SRC_SEL BIT(20) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV (0 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_743MV (1 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_650MV (2 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_682MV (3 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_PULSE_REJECT BIT(17) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_0 (0 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_15P (1 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_30P (3 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_UP BIT(14) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV (0 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_70MV (1 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_EN BIT(12) +#define MIPI_CSIS_DPHY_BCTRL_L_ERRCONTENTION_LP_EN BIT(11) +#define MIPI_CSIS_DPHY_BCTRL_L_TXTRIGGER_CLK_EN BIT(10) +#define MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(n) (((n) * 25 / 1000000) << 0) + +/* D-PHY Master and Slave Control register High */ +#define MIPI_CSIS_DPHY_BCTRL_H 0x34 +/* D-PHY Slave Control register Low */ +#define MIPI_CSIS_DPHY_SCTRL_L 0x38 +/* D-PHY Slave Control register High */ +#define MIPI_CSIS_DPHY_SCTRL_H 0x3c + +/* ISP Configuration register */ +#define MIPI_CSIS_ISP_CONFIG_CH(n) (0x40 + (n) * 0x10) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP_MSK (0xff << 24) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP(x) ((x) << 24) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_SINGLE (0 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL (1 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_QUAD (2 << 12) /* i.MX8M[MNP] only */ +#define MIPI_CSIS_ISPCFG_PIXEL_MASK (3 << 12) +#define MIPI_CSIS_ISPCFG_ALIGN_32BIT BIT(11) +#define MIPI_CSIS_ISPCFG_FMT(fmt) ((fmt) << 2) +#define MIPI_CSIS_ISPCFG_FMT_MASK (0x3f << 2) + +/* ISP Image Resolution register */ +#define MIPI_CSIS_ISP_RESOL_CH(n) (0x44 + (n) * 0x10) +#define CSIS_MAX_PIX_WIDTH 0xffff +#define CSIS_MAX_PIX_HEIGHT 0xffff + +/* ISP SYNC register */ +#define MIPI_CSIS_ISP_SYNC_CH(n) (0x48 + (n) * 0x10) +#define MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET 18 +#define MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET 12 +#define MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET 0 + +/* ISP shadow registers */ +#define MIPI_CSIS_SDW_CONFIG_CH(n) (0x80 + (n) * 0x10) +#define MIPI_CSIS_SDW_RESOL_CH(n) (0x84 + (n) * 0x10) +#define MIPI_CSIS_SDW_SYNC_CH(n) (0x88 + (n) * 0x10) + +/* Debug control register */ +#define MIPI_CSIS_DBG_CTRL 0xc0 +#define MIPI_CSIS_DBG_INTR_MSK 0xc4 +#define MIPI_CSIS_DBG_INTR_MSK_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_MSK_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_MSK_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_MSK_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_RISE BIT(0) +#define MIPI_CSIS_DBG_INTR_SRC 0xc8 +#define MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE BIT(0) + +#define MIPI_CSIS_FRAME_COUNTER_CH(n) (0x0100 + (n) * 4) + +/* Non-image packet data buffers */ +#define MIPI_CSIS_PKTDATA_ODD 0x2000 +#define MIPI_CSIS_PKTDATA_EVEN 0x3000 +#define MIPI_CSIS_PKTDATA_SIZE SZ_4K + +#define DEFAULT_SCLK_CSIS_FREQ 166000000UL + +/* MIPI CSI-2 Data Types */ +#define MIPI_CSI2_DATA_TYPE_YUV420_8 0x18 +#define MIPI_CSI2_DATA_TYPE_YUV420_10 0x19 +#define MIPI_CSI2_DATA_TYPE_LE_YUV420_8 0x1a +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_8 0x1c +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_10 0x1d +#define MIPI_CSI2_DATA_TYPE_YUV422_8 0x1e +#define MIPI_CSI2_DATA_TYPE_YUV422_10 0x1f +#define MIPI_CSI2_DATA_TYPE_RGB565 0x22 +#define MIPI_CSI2_DATA_TYPE_RGB666 0x23 +#define MIPI_CSI2_DATA_TYPE_RGB888 0x24 +#define MIPI_CSI2_DATA_TYPE_RAW6 0x28 +#define MIPI_CSI2_DATA_TYPE_RAW7 0x29 +#define MIPI_CSI2_DATA_TYPE_RAW8 0x2a +#define MIPI_CSI2_DATA_TYPE_RAW10 0x2b +#define MIPI_CSI2_DATA_TYPE_RAW12 0x2c +#define MIPI_CSI2_DATA_TYPE_RAW14 0x2d +#define MIPI_CSI2_DATA_TYPE_USER(x) (0x30 + (x)) + +enum { + ST_POWERED = 1, + ST_STREAMING = 2, + ST_SUSPENDED = 4, +}; + +struct mipi_csis_event { + bool debug; + u32 mask; + const char * const name; + unsigned int counter; +}; + +static const struct mipi_csis_event mipi_csis_events[] = { + /* Errors */ + { false, MIPI_CSIS_INT_SRC_ERR_SOT_HS, "SOT Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FS, "Lost Frame Start Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FE, "Lost Frame End Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_OVER, "FIFO Overflow Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_WRONG_CFG, "Wrong Configuration Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_ECC, "ECC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_CRC, "CRC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_UNKNOWN, "Unknown Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT, "Data Type Not Supported" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE, "Data Type Ignored" }, + { true, MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE, "Frame Size Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME, "Truncated Frame" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FE, "Early Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FS, "Early Frame Start" }, + /* Non-image data receive events */ + { false, MIPI_CSIS_INT_SRC_EVEN_BEFORE, "Non-image data before even frame" }, + { false, MIPI_CSIS_INT_SRC_EVEN_AFTER, "Non-image data after even frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_BEFORE, "Non-image data before odd frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_AFTER, "Non-image data after odd frame" }, + /* Frame start/end */ + { false, MIPI_CSIS_INT_SRC_FRAME_START, "Frame Start" }, + { false, MIPI_CSIS_INT_SRC_FRAME_END, "Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL, "VSYNC Falling Edge" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE, "VSYNC Rising Edge" }, +}; + +#define MIPI_CSIS_NUM_EVENTS ARRAY_SIZE(mipi_csis_events) + +enum mipi_csis_clk { + MIPI_CSIS_CLK_PCLK, + MIPI_CSIS_CLK_WRAP, + MIPI_CSIS_CLK_PHY, + MIPI_CSIS_CLK_AXI, +}; + +static const char * const mipi_csis_clk_id[] = { + "pclk", + "wrap", + "phy", + "axi", +}; + +enum mipi_csis_version { + MIPI_CSIS_V3_3, + MIPI_CSIS_V3_6_3, +}; + +struct mipi_csis_info { + enum mipi_csis_version version; + unsigned int num_clocks; +}; + +struct csi_state { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clks; + struct reset_control *mrst; + struct regulator *mipi_phy_regulator; + const struct mipi_csis_info *info; + + struct v4l2_subdev sd; + struct media_pad pads[CSIS_PADS_NUM]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + + struct v4l2_mbus_config_mipi_csi2 bus; + u32 clk_frequency; + u32 hs_settle; + u32 clk_settle; + + struct mutex lock; /* Protect csis_fmt, format_mbus and state */ + const struct csis_pix_format *csis_fmt; + struct v4l2_mbus_framefmt format_mbus[CSIS_PADS_NUM]; + u32 state; + + spinlock_t slock; /* Protect events */ + struct mipi_csis_event events[MIPI_CSIS_NUM_EVENTS]; + struct dentry *debugfs_root; + struct { + bool enable; + u32 hs_settle; + u32 clk_settle; + } debug; +}; + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct csis_pix_format { + u32 code; + u32 output; + u32 data_type; + u8 width; +}; + +static const struct csis_pix_format mipi_csis_formats[] = { + /* YUV formats. */ + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .output = MEDIA_BUS_FMT_UYVY8_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_YUV422_8, + .width = 16, + }, + /* RGB formats. */ + { + .code = MEDIA_BUS_FMT_RGB565_1X16, + .output = MEDIA_BUS_FMT_RGB565_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_RGB565, + .width = 16, + }, { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .output = MEDIA_BUS_FMT_RGB888_1X24, + .data_type = MIPI_CSI2_DATA_TYPE_RGB888, + .width = 24, + }, + /* RAW (Bayer and greyscale) formats. */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .output = MEDIA_BUS_FMT_SBGGR8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .output = MEDIA_BUS_FMT_SGBRG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .output = MEDIA_BUS_FMT_SGRBG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .output = MEDIA_BUS_FMT_SRGGB8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .output = MEDIA_BUS_FMT_Y8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .output = MEDIA_BUS_FMT_SBGGR10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .output = MEDIA_BUS_FMT_SGBRG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .output = MEDIA_BUS_FMT_SGRBG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .output = MEDIA_BUS_FMT_SRGGB10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .output = MEDIA_BUS_FMT_Y10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .output = MEDIA_BUS_FMT_SBGGR12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .output = MEDIA_BUS_FMT_SGBRG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .output = MEDIA_BUS_FMT_SGRBG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .output = MEDIA_BUS_FMT_SRGGB12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .output = MEDIA_BUS_FMT_Y12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .output = MEDIA_BUS_FMT_SBGGR14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .output = MEDIA_BUS_FMT_SGBRG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .output = MEDIA_BUS_FMT_SGRBG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .output = MEDIA_BUS_FMT_SRGGB14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + } +}; + +static const struct csis_pix_format *find_csis_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mipi_csis_formats); i++) + if (code == mipi_csis_formats[i].code) + return &mipi_csis_formats[i]; + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static inline u32 mipi_csis_read(struct csi_state *state, u32 reg) +{ + return readl(state->regs + reg); +} + +static inline void mipi_csis_write(struct csi_state *state, u32 reg, u32 val) +{ + writel(val, state->regs + reg); +} + +static void mipi_csis_enable_interrupts(struct csi_state *state, bool on) +{ + mipi_csis_write(state, MIPI_CSIS_INT_MSK, on ? 0xffffffff : 0); + mipi_csis_write(state, MIPI_CSIS_DBG_INTR_MSK, on ? 0xffffffff : 0); +} + +static void mipi_csis_sw_reset(struct csi_state *state) +{ + u32 val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL); + + mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_RESET); + usleep_range(10, 20); +} + +static void mipi_csis_system_enable(struct csi_state *state, int on) +{ + u32 val, mask; + + val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL); + if (on) + val |= MIPI_CSIS_CMN_CTRL_ENABLE; + else + val &= ~MIPI_CSIS_CMN_CTRL_ENABLE; + mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val); + + val = mipi_csis_read(state, MIPI_CSIS_DPHY_CMN_CTRL); + val &= ~MIPI_CSIS_DPHY_CMN_CTRL_ENABLE; + if (on) { + mask = (1 << (state->bus.num_data_lanes + 1)) - 1; + val |= (mask & MIPI_CSIS_DPHY_CMN_CTRL_ENABLE); + } + mipi_csis_write(state, MIPI_CSIS_DPHY_CMN_CTRL, val); +} + +/* Called with the state.lock mutex held */ +static void __mipi_csis_set_format(struct csi_state *state) +{ + struct v4l2_mbus_framefmt *mf = &state->format_mbus[CSIS_PAD_SINK]; + u32 val; + + /* Color format */ + val = mipi_csis_read(state, MIPI_CSIS_ISP_CONFIG_CH(0)); + val &= ~(MIPI_CSIS_ISPCFG_ALIGN_32BIT | MIPI_CSIS_ISPCFG_FMT_MASK + | MIPI_CSIS_ISPCFG_PIXEL_MASK); + + /* + * YUV 4:2:2 can be transferred with 8 or 16 bits per clock sample + * (referred to in the documentation as single and dual pixel modes + * respectively, although the 8-bit mode transfers half a pixel per + * clock sample and the 16-bit mode one pixel). While both mode work + * when the CSIS is connected to a receiver that supports either option, + * single pixel mode requires clock rates twice as high. As all SoCs + * that integrate the CSIS can operate in 16-bit bit mode, and some do + * not support 8-bit mode (this is the case of the i.MX8MP), use dual + * pixel mode unconditionally. + * + * TODO: Verify which other formats require DUAL (or QUAD) modes. + */ + if (state->csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) + val |= MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL; + + val |= MIPI_CSIS_ISPCFG_FMT(state->csis_fmt->data_type); + mipi_csis_write(state, MIPI_CSIS_ISP_CONFIG_CH(0), val); + + /* Pixel resolution */ + val = mf->width | (mf->height << 16); + mipi_csis_write(state, MIPI_CSIS_ISP_RESOL_CH(0), val); +} + +static int mipi_csis_calculate_params(struct csi_state *state) +{ + s64 link_freq; + u32 lane_rate; + + /* Calculate the line rate from the pixel rate. */ + link_freq = v4l2_get_link_freq(state->src_sd->ctrl_handler, + state->csis_fmt->width, + state->bus.num_data_lanes * 2); + if (link_freq < 0) { + dev_err(state->dev, "Unable to obtain link frequency: %d\n", + (int)link_freq); + return link_freq; + } + + lane_rate = link_freq * 2; + + if (lane_rate < 80000000 || lane_rate > 1500000000) { + dev_dbg(state->dev, "Out-of-bound lane rate %u\n", lane_rate); + return -EINVAL; + } + + /* + * The HSSETTLE counter value is document in a table, but can also + * easily be calculated. Hardcode the CLKSETTLE value to 0 for now + * (which is documented as corresponding to CSI-2 v0.87 to v1.00) until + * we figure out how to compute it correctly. + */ + state->hs_settle = (lane_rate - 5000000) / 45000000; + state->clk_settle = 0; + + dev_dbg(state->dev, "lane rate %u, Tclk_settle %u, Ths_settle %u\n", + lane_rate, state->clk_settle, state->hs_settle); + + if (state->debug.hs_settle < 0xff) { + dev_dbg(state->dev, "overriding Ths_settle with %u\n", + state->debug.hs_settle); + state->hs_settle = state->debug.hs_settle; + } + + if (state->debug.clk_settle < 4) { + dev_dbg(state->dev, "overriding Tclk_settle with %u\n", + state->debug.clk_settle); + state->clk_settle = state->debug.clk_settle; + } + + return 0; +} + +static void mipi_csis_set_params(struct csi_state *state) +{ + int lanes = state->bus.num_data_lanes; + u32 val; + + val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL); + val &= ~MIPI_CSIS_CMN_CTRL_LANE_NR_MASK; + val |= (lanes - 1) << MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET; + if (state->info->version == MIPI_CSIS_V3_3) + val |= MIPI_CSIS_CMN_CTRL_INTER_MODE; + mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val); + + __mipi_csis_set_format(state); + + mipi_csis_write(state, MIPI_CSIS_DPHY_CMN_CTRL, + MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(state->hs_settle) | + MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(state->clk_settle)); + + val = (0 << MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET); + mipi_csis_write(state, MIPI_CSIS_ISP_SYNC_CH(0), val); + + val = mipi_csis_read(state, MIPI_CSIS_CLK_CTRL); + val |= MIPI_CSIS_CLK_CTRL_WCLK_SRC; + val |= MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(15); + val &= ~MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK; + mipi_csis_write(state, MIPI_CSIS_CLK_CTRL, val); + + mipi_csis_write(state, MIPI_CSIS_DPHY_BCTRL_L, + MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV | + MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ | + MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV | + MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(20000000)); + mipi_csis_write(state, MIPI_CSIS_DPHY_BCTRL_H, 0); + + /* Update the shadow register. */ + val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL); + mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW | + MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL); +} + +static int mipi_csis_clk_enable(struct csi_state *state) +{ + return clk_bulk_prepare_enable(state->info->num_clocks, state->clks); +} + +static void mipi_csis_clk_disable(struct csi_state *state) +{ + clk_bulk_disable_unprepare(state->info->num_clocks, state->clks); +} + +static int mipi_csis_clk_get(struct csi_state *state) +{ + unsigned int i; + int ret; + + state->clks = devm_kcalloc(state->dev, state->info->num_clocks, + sizeof(*state->clks), GFP_KERNEL); + + if (!state->clks) + return -ENOMEM; + + for (i = 0; i < state->info->num_clocks; i++) + state->clks[i].id = mipi_csis_clk_id[i]; + + ret = devm_clk_bulk_get(state->dev, state->info->num_clocks, + state->clks); + if (ret < 0) + return ret; + + /* Set clock rate */ + ret = clk_set_rate(state->clks[MIPI_CSIS_CLK_WRAP].clk, + state->clk_frequency); + if (ret < 0) + dev_err(state->dev, "set rate=%d failed: %d\n", + state->clk_frequency, ret); + + return ret; +} + +static void mipi_csis_start_stream(struct csi_state *state) +{ + mipi_csis_sw_reset(state); + mipi_csis_set_params(state); + mipi_csis_system_enable(state, true); + mipi_csis_enable_interrupts(state, true); +} + +static void mipi_csis_stop_stream(struct csi_state *state) +{ + mipi_csis_enable_interrupts(state, false); + mipi_csis_system_enable(state, false); +} + +static irqreturn_t mipi_csis_irq_handler(int irq, void *dev_id) +{ + struct csi_state *state = dev_id; + unsigned long flags; + unsigned int i; + u32 status; + u32 dbg_status; + + status = mipi_csis_read(state, MIPI_CSIS_INT_SRC); + dbg_status = mipi_csis_read(state, MIPI_CSIS_DBG_INTR_SRC); + + spin_lock_irqsave(&state->slock, flags); + + /* Update the event/error counters */ + if ((status & MIPI_CSIS_INT_SRC_ERRORS) || state->debug.enable) { + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) { + struct mipi_csis_event *event = &state->events[i]; + + if ((!event->debug && (status & event->mask)) || + (event->debug && (dbg_status & event->mask))) + event->counter++; + } + } + spin_unlock_irqrestore(&state->slock, flags); + + mipi_csis_write(state, MIPI_CSIS_INT_SRC, status); + mipi_csis_write(state, MIPI_CSIS_DBG_INTR_SRC, dbg_status); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * PHY regulator and reset + */ + +static int mipi_csis_phy_enable(struct csi_state *state) +{ + if (state->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_enable(state->mipi_phy_regulator); +} + +static int mipi_csis_phy_disable(struct csi_state *state) +{ + if (state->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_disable(state->mipi_phy_regulator); +} + +static void mipi_csis_phy_reset(struct csi_state *state) +{ + if (state->info->version != MIPI_CSIS_V3_3) + return; + + reset_control_assert(state->mrst); + msleep(20); + reset_control_deassert(state->mrst); +} + +static int mipi_csis_phy_init(struct csi_state *state) +{ + if (state->info->version != MIPI_CSIS_V3_3) + return 0; + + /* Get MIPI PHY reset and regulator. */ + state->mrst = devm_reset_control_get_exclusive(state->dev, NULL); + if (IS_ERR(state->mrst)) + return PTR_ERR(state->mrst); + + state->mipi_phy_regulator = devm_regulator_get(state->dev, "phy"); + if (IS_ERR(state->mipi_phy_regulator)) + return PTR_ERR(state->mipi_phy_regulator); + + return regulator_set_voltage(state->mipi_phy_regulator, 1000000, + 1000000); +} + +/* ----------------------------------------------------------------------------- + * Debug + */ + +static void mipi_csis_clear_counters(struct csi_state *state) +{ + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->slock, flags); + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) + state->events[i].counter = 0; + spin_unlock_irqrestore(&state->slock, flags); +} + +static void mipi_csis_log_counters(struct csi_state *state, bool non_errors) +{ + unsigned int num_events = non_errors ? MIPI_CSIS_NUM_EVENTS + : MIPI_CSIS_NUM_EVENTS - 8; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->slock, flags); + + for (i = 0; i < num_events; ++i) { + if (state->events[i].counter > 0 || state->debug.enable) + dev_info(state->dev, "%s events: %d\n", + state->events[i].name, + state->events[i].counter); + } + spin_unlock_irqrestore(&state->slock, flags); +} + +static int mipi_csis_dump_regs(struct csi_state *state) +{ + static const struct { + u32 offset; + const char * const name; + } registers[] = { + { MIPI_CSIS_CMN_CTRL, "CMN_CTRL" }, + { MIPI_CSIS_CLK_CTRL, "CLK_CTRL" }, + { MIPI_CSIS_INT_MSK, "INT_MSK" }, + { MIPI_CSIS_DPHY_STATUS, "DPHY_STATUS" }, + { MIPI_CSIS_DPHY_CMN_CTRL, "DPHY_CMN_CTRL" }, + { MIPI_CSIS_DPHY_SCTRL_L, "DPHY_SCTRL_L" }, + { MIPI_CSIS_DPHY_SCTRL_H, "DPHY_SCTRL_H" }, + { MIPI_CSIS_ISP_CONFIG_CH(0), "ISP_CONFIG_CH0" }, + { MIPI_CSIS_ISP_RESOL_CH(0), "ISP_RESOL_CH0" }, + { MIPI_CSIS_SDW_CONFIG_CH(0), "SDW_CONFIG_CH0" }, + { MIPI_CSIS_SDW_RESOL_CH(0), "SDW_RESOL_CH0" }, + { MIPI_CSIS_DBG_CTRL, "DBG_CTRL" }, + { MIPI_CSIS_FRAME_COUNTER_CH(0), "FRAME_COUNTER_CH0" }, + }; + + unsigned int i; + u32 cfg; + + dev_info(state->dev, "--- REGISTERS ---\n"); + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + cfg = mipi_csis_read(state, registers[i].offset); + dev_info(state->dev, "%14s: 0x%08x\n", registers[i].name, cfg); + } + + return 0; +} + +static int mipi_csis_dump_regs_show(struct seq_file *m, void *private) +{ + struct csi_state *state = m->private; + + return mipi_csis_dump_regs(state); +} +DEFINE_SHOW_ATTRIBUTE(mipi_csis_dump_regs); + +static void mipi_csis_debugfs_init(struct csi_state *state) +{ + state->debug.hs_settle = UINT_MAX; + state->debug.clk_settle = UINT_MAX; + + state->debugfs_root = debugfs_create_dir(dev_name(state->dev), NULL); + + debugfs_create_bool("debug_enable", 0600, state->debugfs_root, + &state->debug.enable); + debugfs_create_file("dump_regs", 0600, state->debugfs_root, state, + &mipi_csis_dump_regs_fops); + debugfs_create_u32("tclk_settle", 0600, state->debugfs_root, + &state->debug.clk_settle); + debugfs_create_u32("ths_settle", 0600, state->debugfs_root, + &state->debug.hs_settle); +} + +static void mipi_csis_debugfs_exit(struct csi_state *state) +{ + debugfs_remove_recursive(state->debugfs_root); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct csi_state *mipi_sd_to_csis_state(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_state, sd); +} + +static int mipi_csis_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + int ret; + + if (enable) { + ret = mipi_csis_calculate_params(state); + if (ret < 0) + return ret; + + mipi_csis_clear_counters(state); + + ret = pm_runtime_resume_and_get(state->dev); + if (ret < 0) + return ret; + + ret = v4l2_subdev_call(state->src_sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto done; + } + + mutex_lock(&state->lock); + + if (enable) { + if (state->state & ST_SUSPENDED) { + ret = -EBUSY; + goto unlock; + } + + mipi_csis_start_stream(state); + ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1); + if (ret < 0) + goto unlock; + + mipi_csis_log_counters(state, true); + + state->state |= ST_STREAMING; + } else { + v4l2_subdev_call(state->src_sd, video, s_stream, 0); + ret = v4l2_subdev_call(state->src_sd, core, s_power, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + mipi_csis_stop_stream(state); + state->state &= ~ST_STREAMING; + if (state->debug.enable) + mipi_csis_log_counters(state, true); + } + +unlock: + mutex_unlock(&state->lock); + +done: + if (!enable || ret < 0) + pm_runtime_put(state->dev); + + return ret; +} + +static struct v4l2_mbus_framefmt * +mipi_csis_get_format(struct csi_state *state, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which, + unsigned int pad) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&state->sd, sd_state, pad); + + return &state->format_mbus[pad]; +} + +static int mipi_csis_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + struct v4l2_mbus_framefmt *fmt_sink; + struct v4l2_mbus_framefmt *fmt_source; + enum v4l2_subdev_format_whence which; + + which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt_sink = mipi_csis_get_format(state, sd_state, which, CSIS_PAD_SINK); + + fmt_sink->code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt_sink->width = MIPI_CSIS_DEF_PIX_WIDTH; + fmt_sink->height = MIPI_CSIS_DEF_PIX_HEIGHT; + fmt_sink->field = V4L2_FIELD_NONE; + + fmt_sink->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); + fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); + fmt_sink->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, + fmt_sink->ycbcr_enc); + + /* + * When called from mipi_csis_subdev_init() to initialize the active + * configuration, cfg is NULL, which indicates there's no source pad + * configuration to set. + */ + if (!sd_state) + return 0; + + fmt_source = mipi_csis_get_format(state, sd_state, which, + CSIS_PAD_SOURCE); + *fmt_source = *fmt_sink; + + return 0; +} + +static int mipi_csis_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + struct v4l2_mbus_framefmt *fmt; + + fmt = mipi_csis_get_format(state, sd_state, sdformat->which, + sdformat->pad); + + mutex_lock(&state->lock); + sdformat->format = *fmt; + mutex_unlock(&state->lock); + + return 0; +} + +static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + + /* + * The CSIS can't transcode in any way, the source format is identical + * to the sink format. + */ + if (code->pad == CSIS_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = mipi_csis_get_format(state, sd_state, code->which, + code->pad); + code->code = fmt->code; + return 0; + } + + if (code->pad != CSIS_PAD_SINK) + return -EINVAL; + + if (code->index >= ARRAY_SIZE(mipi_csis_formats)) + return -EINVAL; + + code->code = mipi_csis_formats[code->index].code; + + return 0; +} + +static int mipi_csis_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + struct csis_pix_format const *csis_fmt; + struct v4l2_mbus_framefmt *fmt; + unsigned int align; + + /* + * The CSIS can't transcode in any way, the source format can't be + * modified. + */ + if (sdformat->pad == CSIS_PAD_SOURCE) + return mipi_csis_get_fmt(sd, sd_state, sdformat); + + if (sdformat->pad != CSIS_PAD_SINK) + return -EINVAL; + + /* + * Validate the media bus code and clamp and align the size. + * + * The total number of bits per line must be a multiple of 8. We thus + * need to align the width for formats that are not multiples of 8 + * bits. + */ + csis_fmt = find_csis_format(sdformat->format.code); + if (!csis_fmt) + csis_fmt = &mipi_csis_formats[0]; + + switch (csis_fmt->width % 8) { + case 0: + align = 0; + break; + case 4: + align = 1; + break; + case 2: + case 6: + align = 2; + break; + default: + /* 1, 3, 5, 7 */ + align = 3; + break; + } + + v4l_bound_align_image(&sdformat->format.width, 1, + CSIS_MAX_PIX_WIDTH, align, + &sdformat->format.height, 1, + CSIS_MAX_PIX_HEIGHT, 0, 0); + + fmt = mipi_csis_get_format(state, sd_state, sdformat->which, + sdformat->pad); + + mutex_lock(&state->lock); + + fmt->code = csis_fmt->code; + fmt->width = sdformat->format.width; + fmt->height = sdformat->format.height; + fmt->colorspace = sdformat->format.colorspace; + fmt->quantization = sdformat->format.quantization; + fmt->xfer_func = sdformat->format.xfer_func; + fmt->ycbcr_enc = sdformat->format.ycbcr_enc; + + sdformat->format = *fmt; + + /* Propagate the format from sink to source. */ + fmt = mipi_csis_get_format(state, sd_state, sdformat->which, + CSIS_PAD_SOURCE); + *fmt = sdformat->format; + + /* The format on the source pad might change due to unpacking. */ + fmt->code = csis_fmt->output; + + /* Store the CSIS format descriptor for active formats. */ + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + state->csis_fmt = csis_fmt; + + mutex_unlock(&state->lock); + + return 0; +} + +static int mipi_csis_log_status(struct v4l2_subdev *sd) +{ + struct csi_state *state = mipi_sd_to_csis_state(sd); + + mutex_lock(&state->lock); + mipi_csis_log_counters(state, true); + if (state->debug.enable && (state->state & ST_POWERED)) + mipi_csis_dump_regs(state); + mutex_unlock(&state->lock); + + return 0; +} + +static const struct v4l2_subdev_core_ops mipi_csis_core_ops = { + .log_status = mipi_csis_log_status, +}; + +static const struct v4l2_subdev_video_ops mipi_csis_video_ops = { + .s_stream = mipi_csis_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = { + .init_cfg = mipi_csis_init_cfg, + .enum_mbus_code = mipi_csis_enum_mbus_code, + .get_fmt = mipi_csis_get_fmt, + .set_fmt = mipi_csis_set_fmt, +}; + +static const struct v4l2_subdev_ops mipi_csis_subdev_ops = { + .core = &mipi_csis_core_ops, + .video = &mipi_csis_video_ops, + .pad = &mipi_csis_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static int mipi_csis_link_setup(struct media_entity *entity, + const struct media_pad *local_pad, + const struct media_pad *remote_pad, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csi_state *state = mipi_sd_to_csis_state(sd); + struct v4l2_subdev *remote_sd; + + dev_dbg(state->dev, "link setup %s -> %s", remote_pad->entity->name, + local_pad->entity->name); + + /* We only care about the link to the source. */ + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) + return 0; + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (state->src_sd) + return -EBUSY; + + state->src_sd = remote_sd; + } else { + state->src_sd = NULL; + } + + return 0; +} + +static const struct media_entity_operations mipi_csis_entity_ops = { + .link_setup = mipi_csis_link_setup, + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Async subdev notifier + */ + +static struct csi_state * +mipi_notifier_to_csis_state(struct v4l2_async_notifier *n) +{ + return container_of(n, struct csi_state, notifier); +} + +static int mipi_csis_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct csi_state *state = mipi_notifier_to_csis_state(notifier); + struct media_pad *sink = &state->sd.entity.pads[CSIS_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); +} + +static const struct v4l2_async_notifier_operations mipi_csis_notify_ops = { + .bound = mipi_csis_notify_bound, +}; + +static int mipi_csis_async_register(struct csi_state *state) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + unsigned int i; + int ret; + + v4l2_async_nf_init(&state->notifier); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { + dev_err(state->dev, + "data lanes reordering is not supported"); + ret = -EINVAL; + goto err_parse; + } + } + + state->bus = vep.bus.mipi_csi2; + + dev_dbg(state->dev, "data lanes: %d\n", state->bus.num_data_lanes); + dev_dbg(state->dev, "flags: 0x%08x\n", state->bus.flags); + + asd = v4l2_async_nf_add_fwnode_remote(&state->notifier, ep, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_parse; + } + + fwnode_handle_put(ep); + + state->notifier.ops = &mipi_csis_notify_ops; + + ret = v4l2_async_subdev_nf_register(&state->sd, &state->notifier); + if (ret) + return ret; + + return v4l2_async_register_subdev(&state->sd); + +err_parse: + fwnode_handle_put(ep); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend/resume + */ + +static int mipi_csis_pm_suspend(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csis_state(sd); + int ret = 0; + + mutex_lock(&state->lock); + if (state->state & ST_POWERED) { + mipi_csis_stop_stream(state); + ret = mipi_csis_phy_disable(state); + if (ret) + goto unlock; + mipi_csis_clk_disable(state); + state->state &= ~ST_POWERED; + if (!runtime) + state->state |= ST_SUSPENDED; + } + +unlock: + mutex_unlock(&state->lock); + + return ret ? -EAGAIN : 0; +} + +static int mipi_csis_pm_resume(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csis_state(sd); + int ret = 0; + + mutex_lock(&state->lock); + if (!runtime && !(state->state & ST_SUSPENDED)) + goto unlock; + + if (!(state->state & ST_POWERED)) { + ret = mipi_csis_phy_enable(state); + if (ret) + goto unlock; + + state->state |= ST_POWERED; + mipi_csis_clk_enable(state); + } + if (state->state & ST_STREAMING) + mipi_csis_start_stream(state); + + state->state &= ~ST_SUSPENDED; + +unlock: + mutex_unlock(&state->lock); + + return ret ? -EAGAIN : 0; +} + +static int __maybe_unused mipi_csis_suspend(struct device *dev) +{ + return mipi_csis_pm_suspend(dev, false); +} + +static int __maybe_unused mipi_csis_resume(struct device *dev) +{ + return mipi_csis_pm_resume(dev, false); +} + +static int __maybe_unused mipi_csis_runtime_suspend(struct device *dev) +{ + return mipi_csis_pm_suspend(dev, true); +} + +static int __maybe_unused mipi_csis_runtime_resume(struct device *dev) +{ + return mipi_csis_pm_resume(dev, true); +} + +static const struct dev_pm_ops mipi_csis_pm_ops = { + SET_RUNTIME_PM_OPS(mipi_csis_runtime_suspend, mipi_csis_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(mipi_csis_suspend, mipi_csis_resume) +}; + +/* ----------------------------------------------------------------------------- + * Probe/remove & platform driver + */ + +static int mipi_csis_subdev_init(struct csi_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + v4l2_subdev_init(sd, &mipi_csis_subdev_ops); + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "csis-%s", + dev_name(state->dev)); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->ctrl_handler = NULL; + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &mipi_csis_entity_ops; + + sd->dev = state->dev; + + state->csis_fmt = &mipi_csis_formats[0]; + mipi_csis_init_cfg(sd, NULL); + + state->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + state->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; + return media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, + state->pads); +} + +static int mipi_csis_parse_dt(struct csi_state *state) +{ + struct device_node *node = state->dev->of_node; + + if (of_property_read_u32(node, "clock-frequency", + &state->clk_frequency)) + state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ; + + return 0; +} + +static int mipi_csis_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi_state *state; + int irq; + int ret; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + spin_lock_init(&state->slock); + + state->dev = dev; + state->info = of_device_get_match_data(dev); + + memcpy(state->events, mipi_csis_events, sizeof(state->events)); + + /* Parse DT properties. */ + ret = mipi_csis_parse_dt(state); + if (ret < 0) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + /* Acquire resources. */ + state->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = mipi_csis_phy_init(state); + if (ret < 0) + return ret; + + ret = mipi_csis_clk_get(state); + if (ret < 0) + return ret; + + /* Reset PHY and enable the clocks. */ + mipi_csis_phy_reset(state); + + ret = mipi_csis_clk_enable(state); + if (ret < 0) { + dev_err(state->dev, "failed to enable clocks: %d\n", ret); + return ret; + } + + /* Now that the hardware is initialized, request the interrupt. */ + ret = devm_request_irq(dev, irq, mipi_csis_irq_handler, 0, + dev_name(dev), state); + if (ret) { + dev_err(dev, "Interrupt request failed\n"); + goto disable_clock; + } + + /* Initialize and register the subdev. */ + ret = mipi_csis_subdev_init(state); + if (ret < 0) + goto disable_clock; + + platform_set_drvdata(pdev, &state->sd); + + ret = mipi_csis_async_register(state); + if (ret < 0) { + dev_err(dev, "async register failed: %d\n", ret); + goto cleanup; + } + + /* Initialize debugfs. */ + mipi_csis_debugfs_init(state); + + /* Enable runtime PM. */ + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = mipi_csis_pm_resume(dev, true); + if (ret < 0) + goto unregister_all; + } + + dev_info(dev, "lanes: %d, freq: %u\n", + state->bus.num_data_lanes, state->clk_frequency); + + return 0; + +unregister_all: + mipi_csis_debugfs_exit(state); +cleanup: + media_entity_cleanup(&state->sd.entity); + v4l2_async_nf_unregister(&state->notifier); + v4l2_async_nf_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); +disable_clock: + mipi_csis_clk_disable(state); + mutex_destroy(&state->lock); + + return ret; +} + +static int mipi_csis_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi_state *state = mipi_sd_to_csis_state(sd); + + mipi_csis_debugfs_exit(state); + v4l2_async_nf_unregister(&state->notifier); + v4l2_async_nf_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); + + pm_runtime_disable(&pdev->dev); + mipi_csis_pm_suspend(&pdev->dev, true); + mipi_csis_clk_disable(state); + media_entity_cleanup(&state->sd.entity); + mutex_destroy(&state->lock); + pm_runtime_set_suspended(&pdev->dev); + + return 0; +} + +static const struct of_device_id mipi_csis_of_match[] = { + { + .compatible = "fsl,imx7-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_3, + .num_clocks = 3, + }, + }, { + .compatible = "fsl,imx8mm-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_6_3, + .num_clocks = 4, + }, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mipi_csis_of_match); + +static struct platform_driver mipi_csis_driver = { + .probe = mipi_csis_probe, + .remove = mipi_csis_remove, + .driver = { + .of_match_table = mipi_csis_of_match, + .name = CSIS_DRIVER_NAME, + .pm = &mipi_csis_pm_ops, + }, +}; + +module_platform_driver(mipi_csis_driver); + +MODULE_DESCRIPTION("i.MX7 & i.MX8 MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-mipi-csi2"); diff --git a/drivers/media/platform/nxp/imx-pxp.c b/drivers/media/platform/nxp/imx-pxp.c new file mode 100644 index 000000000000..689ae5e6ac62 --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.c @@ -0,0 +1,1788 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX Pixel Pipeline (PXP) mem-to-mem scaler/CSC/rotator driver + * + * Copyright (c) 2018 Pengutronix, Philipp Zabel + * + * based on vim2m + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, <pawel@osciak.com> + * Marek Szyprowski, <m.szyprowski@samsung.com> + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <linux/platform_device.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.h> +#include <media/videobuf2-dma-contig.h> + +#include "imx-pxp.h" + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); + +#define MIN_W 8 +#define MIN_H 8 +#define MAX_W 4096 +#define MAX_H 4096 +#define ALIGN_W 3 /* 8x8 pixel blocks */ +#define ALIGN_H 3 + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "pxp" + +/* Flags that indicate processing mode */ +#define MEM2MEM_HFLIP (1 << 0) +#define MEM2MEM_VFLIP (1 << 1) + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +struct pxp_fmt { + u32 fourcc; + int depth; + /* Types the format can be used for */ + u32 types; +}; + +static struct pxp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = 32, + /* Both capture and output format */ + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + .depth = 32, + /* Capture-only format */ + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VUYA32, + .depth = 32, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_VUYX32, + .depth = 32, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + /* Output-only format */ + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_Y4, + .depth = 4, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .types = MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct pxp_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + unsigned int sequence; + struct pxp_fmt *fmt; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quant; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +static struct pxp_fmt *find_format(struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct pxp_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + + struct clk *clk; + void __iomem *mmio; + + atomic_t num_inst; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct pxp_ctx { + struct v4l2_fh fh; + struct pxp_dev *dev; + + struct v4l2_ctrl_handler hdl; + + /* Abort requested by m2m */ + int aborting; + + /* Processing mode */ + int mode; + u8 alpha_component; + u8 rotation; + + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + + /* Source and destination queue data */ + struct pxp_q_data q_data[2]; +}; + +static inline struct pxp_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct pxp_ctx, fh); +} + +static struct pxp_q_data *get_q_data(struct pxp_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + return &ctx->q_data[V4L2_M2M_SRC]; + else + return &ctx->q_data[V4L2_M2M_DST]; +} + +static u32 pxp_v4l2_pix_fmt_to_ps_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_PS_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_RGB555: return BV_PXP_PS_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_PS_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_PS_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYX32: return BV_PXP_PS_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_YUYV: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_YVYU: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_PS_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_PS_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_PS_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_PS_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV21: return BV_PXP_PS_CTRL_FORMAT__YVU2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_PS_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_YUV422P: return BV_PXP_PS_CTRL_FORMAT__YUV422; + case V4L2_PIX_FMT_YUV420: return BV_PXP_PS_CTRL_FORMAT__YUV420; + } +} + +static u32 pxp_v4l2_pix_fmt_to_out_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_OUT_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_ABGR32: return BV_PXP_OUT_CTRL_FORMAT__ARGB8888; + case V4L2_PIX_FMT_BGR24: return BV_PXP_OUT_CTRL_FORMAT__RGB888P; + /* Missing V4L2 pixel formats for ARGB1555 and ARGB4444 */ + case V4L2_PIX_FMT_RGB555: return BV_PXP_OUT_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_OUT_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_OUT_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: return BV_PXP_OUT_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_OUT_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_OUT_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_OUT_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_OUT_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_OUT_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_OUT_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_OUT_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_NV21: return BV_PXP_OUT_CTRL_FORMAT__YVU2P420; + } +} + +static bool pxp_v4l2_pix_fmt_is_yuv(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + return true; + default: + return false; + } +} + +static void pxp_setup_csc(struct pxp_ctx *ctx) +{ + struct pxp_dev *dev = ctx->dev; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + !pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC1 YUV/YCbCr to RGB conversion is implemented as follows: + * + * |R| |C0 0 C1| |Y + Yoffset | + * |G| = |C0 C3 C2| * |Cb + UVoffset| + * |B| |C0 C4 0 | |Cr + UVoffset| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |R| |1.1644 0.0000 1.5960| |Y - 16 | + * |G| = |1.1644 -0.3917 -0.8129| * |Cb - 128| + * |B| |1.1644 2.0172 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x198) | /* 1.5938 (-0.23 %) */ + BF_PXP_CSC1_COEF1_C4(0x204), /* 2.0156 (-0.16 %) */ + BF_PXP_CSC1_COEF2_C2(0x730) | /* -0.8125 (+0.04 %) */ + BF_PXP_CSC1_COEF2_C3(0x79c), /* -0.3906 (+0.11 %) */ + }; + /* + * BT.601 full range: + * + * |R| |1.0000 0.0000 1.4020| |Y + 0 | + * |G| = |1.0000 -0.3441 -0.7141| * |Cb - 128| + * |B| |1.0000 1.7720 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x166) | /* 1.3984 (-0.36 %) */ + BF_PXP_CSC1_COEF1_C4(0x1c5), /* 1.7695 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x74a) | /* -0.7109 (+0.32 %) */ + BF_PXP_CSC1_COEF2_C3(0x7a8), /* -0.3438 (+0.04 %) */ + }; + /* + * Rec.709 limited range: + * + * |R| |1.1644 0.0000 1.7927| |Y - 16 | + * |G| = |1.1644 -0.2132 -0.5329| * |Cb - 128| + * |B| |1.1644 2.1124 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ca) | /* 1.7891 (-0.37 %) */ + BF_PXP_CSC1_COEF1_C4(0x21c), /* 2.1094 (-0.30 %) */ + BF_PXP_CSC1_COEF2_C2(0x778) | /* -0.5312 (+0.16 %) */ + BF_PXP_CSC1_COEF2_C3(0x7ca), /* -0.2109 (+0.23 %) */ + }; + /* + * Rec.709 full range: + * + * |R| |1.0000 0.0000 1.5748| |Y + 0 | + * |G| = |1.0000 -0.1873 -0.4681| * |Cb - 128| + * |B| |1.0000 1.8556 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.06 %) */ + BF_PXP_CSC1_COEF1_C4(0x1db), /* 1.8555 (-0.01 %) */ + BF_PXP_CSC1_COEF2_C2(0x789) | /* -0.4648 (+0.33 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.37 %) */ + }; + /* + * BT.2020 limited range: + * + * |R| |1.1644 0.0000 1.6787| |Y - 16 | + * |G| = |1.1644 -0.1874 -0.6505| * |Cb - 128| + * |B| |1.1644 2.1418 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ad) | /* 1.6758 (-0.29 %) */ + BF_PXP_CSC1_COEF1_C4(0x224), /* 2.1406 (-0.11 %) */ + BF_PXP_CSC1_COEF2_C2(0x75a) | /* -0.6484 (+0.20 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.38 %) */ + }; + /* + * BT.2020 full range: + * + * |R| |1.0000 0.0000 1.4746| |Y + 0 | + * |G| = |1.0000 -0.1646 -0.5714| * |Cb - 128| + * |B| |1.0000 1.8814 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x179) | /* 1.4727 (-0.19 %) */ + BF_PXP_CSC1_COEF1_C4(0x1e1), /* 1.8789 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x76e) | /* -0.5703 (+0.11 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d6), /* -0.1641 (+0.05 %) */ + }; + /* + * SMPTE 240m limited range: + * + * |R| |1.1644 0.0000 1.7937| |Y - 16 | + * |G| = |1.1644 -0.2565 -0.5427| * |Cb - 128| + * |B| |1.1644 2.0798 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1cb) | /* 1.7930 (-0.07 %) */ + BF_PXP_CSC1_COEF1_C4(0x214), /* 2.0781 (-0.17 %) */ + BF_PXP_CSC1_COEF2_C2(0x776) | /* -0.5391 (+0.36 %) */ + BF_PXP_CSC1_COEF2_C3(0x7bf), /* -0.2539 (+0.26 %) */ + }; + /* + * SMPTE 240m full range: + * + * |R| |1.0000 0.0000 1.5756| |Y + 0 | + * |G| = |1.0000 -0.2253 -0.4767| * |Cb - 128| + * |B| |1.0000 1.8270 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.14 %) */ + BF_PXP_CSC1_COEF1_C4(0x1d3), /* 1.8242 (-0.28 %) */ + BF_PXP_CSC1_COEF2_C2(0x786) | /* -0.4766 (+0.01 %) */ + BF_PXP_CSC1_COEF2_C3(0x7c7), /* -0.2227 (+0.26 %) */ + }; + const u32 *csc1_coef; + + ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_SRC].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt601_full; + else + csc1_coef = csc1_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_rec709_full; + else + csc1_coef = csc1_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt2020_full; + else + csc1_coef = csc1_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_smpte240m_full; + else + csc1_coef = csc1_coef_smpte240m_lim; + } + + writel(csc1_coef[0], dev->mmio + HW_PXP_CSC1_COEF0); + writel(csc1_coef[1], dev->mmio + HW_PXP_CSC1_COEF1); + writel(csc1_coef[2], dev->mmio + HW_PXP_CSC1_COEF2); + } else { + writel(BM_PXP_CSC1_COEF0_BYPASS, dev->mmio + HW_PXP_CSC1_COEF0); + } + + if (!pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC2 RGB to YUV/YCbCr conversion is implemented as follows: + * + * |Y | |A1 A2 A3| |R| |D1| + * |Cb| = |B1 B2 B3| * |G| + |D2| + * |Cr| |C1 C2 C3| |B| |D3| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |Y | | 0.2568 0.5041 0.0979| |R| |16 | + * |Cb| = |-0.1482 -0.2910 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3678| |B| |128| + */ + static const u32 csc2_coef_bt601_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x081) | /* 0.5039 (-0.02 %) */ + BF_PXP_CSC2_COEF0_A1(0x041), /* 0.2539 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7db) | /* -0.1445 (+0.37 %) */ + BF_PXP_CSC2_COEF1_A3(0x019), /* 0.0977 (-0.02 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7b6), /* -0.2891 (+0.20 %) */ + BF_PXP_CSC2_COEF3_C2(0x7a2) | /* -0.3672 (+0.06 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7ee), /* -0.0703 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.601 full range: + * + * |Y | | 0.2990 0.5870 0.1140| |R| |0 | + * |Cb| = |-0.1687 -0.3313 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4187| |B| |128| + */ + static const u32 csc2_coef_bt601_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x096) | /* 0.5859 (-0.11 %) */ + BF_PXP_CSC2_COEF0_A1(0x04c), /* 0.2969 (-0.21 %) */ + BF_PXP_CSC2_COEF1_B1(0x7d5) | /* -0.1680 (+0.07 %) */ + BF_PXP_CSC2_COEF1_A3(0x01d), /* 0.1133 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7ac), /* -0.3281 (+0.32 %) */ + BF_PXP_CSC2_COEF3_C2(0x795) | /* -0.4180 (+0.07 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7ec), /* -0.0781 (+0.32 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 limited range: + * + * |Y | | 0.1826 0.6142 0.0620| |R| |16 | + * |Cb| = |-0.1007 -0.3385 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3990| |B| |128| + */ + static const u32 csc2_coef_rec709_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09d) | /* 0.6133 (-0.09 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e7) | /* -0.0977 (+0.30 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.34 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x79a) | /* -0.3984 (+0.05 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.12 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 full range: + * + * |Y | | 0.2126 0.7152 0.0722| |R| |0 | + * |Cb| = |-0.1146 -0.3854 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4542| |B| |128| + */ + static const u32 csc2_coef_rec709_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b7) | /* 0.7148 (-0.04 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.17 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.13 %) */ + BF_PXP_CSC2_COEF1_A3(0x012), /* 0.0703 (-0.19 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x78c) | /* -0.4531 (+0.11 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f5), /* -0.0430 (+0.28 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 limited range: + * + * |Y | | 0.2256 0.5823 0.0509| |R| |16 | + * |Cb| = |-0.1226 -0.3166 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.4039| |B| |128| + */ + static const u32 csc2_coef_bt2020_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x095) | /* 0.5820 (-0.03 %) */ + BF_PXP_CSC2_COEF0_A1(0x039), /* 0.2227 (-0.30 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e1) | /* -0.1211 (+0.15 %) */ + BF_PXP_CSC2_COEF1_A3(0x00d), /* 0.0508 (-0.01 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7af), /* -0.3164 (+0.02 %) */ + BF_PXP_CSC2_COEF3_C2(0x799) | /* -0.4023 (+0.16 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f7), /* -0.0352 (+0.02 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 full range: + * + * |Y | | 0.2627 0.6780 0.0593| |R| |0 | + * |Cb| = |-0.1396 -0.3604 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4598| |B| |128| + */ + static const u32 csc2_coef_bt2020_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0ad) | /* 0.6758 (-0.22 %) */ + BF_PXP_CSC2_COEF0_A1(0x043), /* 0.2617 (-0.10 %) */ + BF_PXP_CSC2_COEF1_B1(0x7dd) | /* -0.1367 (+0.29 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7a4), /* -0.3594 (+0.10 %) */ + BF_PXP_CSC2_COEF3_C2(0x78b) | /* -0.4570 (+0.28 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m limited range: + * + * |Y | | 0.1821 0.6020 0.0747| |R| |16 | + * |Cb| = |-0.1019 -0.3373 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3909| |B| |128| + */ + static const u32 csc2_coef_smpte240m_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09a) | /* 0.6016 (-0.05 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.24 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e6) | /* -0.1016 (+0.03 %) */ + BF_PXP_CSC2_COEF1_A3(0x013), /* 0.0742 (-0.05 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.14 %) */ + BF_PXP_CSC2_COEF3_C2(0x79c) | /* -0.3906 (+0.03 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f4), /* -0.0469 (+0.14 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m full range: + * + * |Y | | 0.2120 0.7010 0.0870| |R| |0 | + * |Cb| = |-0.1160 -0.3840 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4450| |B| |128| + */ + static const u32 csc2_coef_smpte240m_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b3) | /* 0.6992 (-0.18 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.11 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.27 %) */ + BF_PXP_CSC2_COEF1_A3(0x016), /* 0.0859 (-0.11 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.12 %) */ + BF_PXP_CSC2_COEF3_C2(0x78f) | /* -0.4414 (+0.36 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f2), /* -0.0547 (+0.03 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + const u32 *csc2_coef; + u32 csc2_ctrl; + + ycbcr_enc = ctx->q_data[V4L2_M2M_DST].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_DST].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt601_full; + else + csc2_coef = csc2_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_rec709_full; + else + csc2_coef = csc2_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt2020_full; + else + csc2_coef = csc2_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_smpte240m_full; + else + csc2_coef = csc2_coef_smpte240m_lim; + } + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV << + BP_PXP_CSC2_CTRL_CSC_MODE; + } else { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr << + BP_PXP_CSC2_CTRL_CSC_MODE; + } + + writel(csc2_ctrl, dev->mmio + HW_PXP_CSC2_CTRL); + writel(csc2_coef[0], dev->mmio + HW_PXP_CSC2_COEF0); + writel(csc2_coef[1], dev->mmio + HW_PXP_CSC2_COEF1); + writel(csc2_coef[2], dev->mmio + HW_PXP_CSC2_COEF2); + writel(csc2_coef[3], dev->mmio + HW_PXP_CSC2_COEF3); + writel(csc2_coef[4], dev->mmio + HW_PXP_CSC2_COEF4); + writel(csc2_coef[5], dev->mmio + HW_PXP_CSC2_COEF5); + } else { + writel(BM_PXP_CSC2_CTRL_BYPASS, dev->mmio + HW_PXP_CSC2_CTRL); + } +} + +static int pxp_start(struct pxp_ctx *ctx, struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + u32 src_width, src_height, src_stride, src_fourcc; + u32 dst_width, dst_height, dst_stride, dst_fourcc; + dma_addr_t p_in, p_out; + u32 ctrl, out_ctrl, out_buf, out_buf2, out_pitch, out_lrc, out_ps_ulc; + u32 out_ps_lrc; + u32 ps_ctrl, ps_buf, ps_ubuf, ps_vbuf, ps_pitch, ps_scale, ps_offset; + u32 as_ulc, as_lrc; + u32 y_size; + u32 decx, decy, xscale, yscale; + + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + src_width = ctx->q_data[V4L2_M2M_SRC].width; + dst_width = ctx->q_data[V4L2_M2M_DST].width; + src_height = ctx->q_data[V4L2_M2M_SRC].height; + dst_height = ctx->q_data[V4L2_M2M_DST].height; + src_stride = ctx->q_data[V4L2_M2M_SRC].bytesperline; + dst_stride = ctx->q_data[V4L2_M2M_DST].bytesperline; + src_fourcc = ctx->q_data[V4L2_M2M_SRC].fmt->fourcc; + dst_fourcc = ctx->q_data[V4L2_M2M_DST].fmt->fourcc; + + p_in = vb2_dma_contig_plane_dma_addr(&in_vb->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&out_vb->vb2_buf, 0); + + if (!p_in || !p_out) { + v4l2_err(&dev->v4l2_dev, + "Acquiring DMA addresses of buffers failed\n"); + return -EFAULT; + } + + out_vb->sequence = + get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE)->sequence++; + in_vb->sequence = q_data->sequence++; + out_vb->vb2_buf.timestamp = in_vb->vb2_buf.timestamp; + + if (in_vb->flags & V4L2_BUF_FLAG_TIMECODE) + out_vb->timecode = in_vb->timecode; + out_vb->field = in_vb->field; + out_vb->flags = in_vb->flags & + (V4L2_BUF_FLAG_TIMECODE | + V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + /* 8x8 block size */ + ctrl = BF_PXP_CTRL_VFLIP0(!!(ctx->mode & MEM2MEM_VFLIP)) | + BF_PXP_CTRL_HFLIP0(!!(ctx->mode & MEM2MEM_HFLIP)) | + BF_PXP_CTRL_ROTATE0(ctx->rotation); + /* Always write alpha value as V4L2_CID_ALPHA_COMPONENT */ + out_ctrl = BF_PXP_OUT_CTRL_ALPHA(ctx->alpha_component) | + BF_PXP_OUT_CTRL_ALPHA_OUTPUT(1) | + pxp_v4l2_pix_fmt_to_out_format(dst_fourcc); + out_buf = p_out; + + if (ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_90 || + ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_270) + swap(dst_width, dst_height); + + switch (dst_fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + out_buf2 = out_buf + dst_stride * dst_height; + break; + default: + out_buf2 = 0; + } + + out_pitch = BF_PXP_OUT_PITCH_PITCH(dst_stride); + out_lrc = BF_PXP_OUT_LRC_X(dst_width - 1) | + BF_PXP_OUT_LRC_Y(dst_height - 1); + /* PS covers whole output */ + out_ps_ulc = BF_PXP_OUT_PS_ULC_X(0) | BF_PXP_OUT_PS_ULC_Y(0); + out_ps_lrc = BF_PXP_OUT_PS_LRC_X(dst_width - 1) | + BF_PXP_OUT_PS_LRC_Y(dst_height - 1); + /* no AS */ + as_ulc = BF_PXP_OUT_AS_ULC_X(1) | BF_PXP_OUT_AS_ULC_Y(1); + as_lrc = BF_PXP_OUT_AS_LRC_X(0) | BF_PXP_OUT_AS_LRC_Y(0); + + decx = (src_width <= dst_width) ? 0 : ilog2(src_width / dst_width); + decy = (src_height <= dst_height) ? 0 : ilog2(src_height / dst_height); + ps_ctrl = BF_PXP_PS_CTRL_DECX(decx) | BF_PXP_PS_CTRL_DECY(decy) | + pxp_v4l2_pix_fmt_to_ps_format(src_fourcc); + ps_buf = p_in; + y_size = src_stride * src_height; + switch (src_fourcc) { + case V4L2_PIX_FMT_YUV420: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 4; + break; + case V4L2_PIX_FMT_YUV422P: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 2; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + ps_ubuf = ps_buf + y_size; + ps_vbuf = 0; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + ps_ubuf = 0; + /* In grayscale mode, ps_vbuf contents are reused as CbCr */ + ps_vbuf = 0x8080; + break; + default: + ps_ubuf = 0; + ps_vbuf = 0; + break; + } + ps_pitch = BF_PXP_PS_PITCH_PITCH(src_stride); + if (decx) { + xscale = (src_width >> decx) * 0x1000 / dst_width; + } else { + switch (src_fourcc) { + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUV420: + /* + * This avoids sampling past the right edge for + * horizontally chroma subsampled formats. + */ + xscale = (src_width - 2) * 0x1000 / (dst_width - 1); + break; + default: + xscale = (src_width - 1) * 0x1000 / (dst_width - 1); + break; + } + } + if (decy) + yscale = (src_height >> decy) * 0x1000 / dst_height; + else + yscale = (src_height - 1) * 0x1000 / (dst_height - 1); + ps_scale = BF_PXP_PS_SCALE_YSCALE(yscale) | + BF_PXP_PS_SCALE_XSCALE(xscale); + ps_offset = BF_PXP_PS_OFFSET_YOFFSET(0) | BF_PXP_PS_OFFSET_XOFFSET(0); + + writel(ctrl, dev->mmio + HW_PXP_CTRL); + /* skip STAT */ + writel(out_ctrl, dev->mmio + HW_PXP_OUT_CTRL); + writel(out_buf, dev->mmio + HW_PXP_OUT_BUF); + writel(out_buf2, dev->mmio + HW_PXP_OUT_BUF2); + writel(out_pitch, dev->mmio + HW_PXP_OUT_PITCH); + writel(out_lrc, dev->mmio + HW_PXP_OUT_LRC); + writel(out_ps_ulc, dev->mmio + HW_PXP_OUT_PS_ULC); + writel(out_ps_lrc, dev->mmio + HW_PXP_OUT_PS_LRC); + writel(as_ulc, dev->mmio + HW_PXP_OUT_AS_ULC); + writel(as_lrc, dev->mmio + HW_PXP_OUT_AS_LRC); + writel(ps_ctrl, dev->mmio + HW_PXP_PS_CTRL); + writel(ps_buf, dev->mmio + HW_PXP_PS_BUF); + writel(ps_ubuf, dev->mmio + HW_PXP_PS_UBUF); + writel(ps_vbuf, dev->mmio + HW_PXP_PS_VBUF); + writel(ps_pitch, dev->mmio + HW_PXP_PS_PITCH); + writel(0x00ffffff, dev->mmio + HW_PXP_PS_BACKGROUND_0); + writel(ps_scale, dev->mmio + HW_PXP_PS_SCALE); + writel(ps_offset, dev->mmio + HW_PXP_PS_OFFSET); + /* disable processed surface color keying */ + writel(0x00ffffff, dev->mmio + HW_PXP_PS_CLRKEYLOW_0); + writel(0x00000000, dev->mmio + HW_PXP_PS_CLRKEYHIGH_0); + + /* disable alpha surface color keying */ + writel(0x00ffffff, dev->mmio + HW_PXP_AS_CLRKEYLOW_0); + writel(0x00000000, dev->mmio + HW_PXP_AS_CLRKEYHIGH_0); + + /* setup CSC */ + pxp_setup_csc(ctx); + + /* bypass LUT */ + writel(BM_PXP_LUT_CTRL_BYPASS, dev->mmio + HW_PXP_LUT_CTRL); + + writel(BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(1)| + BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(1)| + BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(0), + dev->mmio + HW_PXP_DATA_PATH_CTRL0); + writel(BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(1) | + BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(1), + dev->mmio + HW_PXP_DATA_PATH_CTRL1); + + writel(0xffff, dev->mmio + HW_PXP_IRQ_MASK); + + /* ungate, enable PS/AS/OUT and PXP operation */ + writel(BM_PXP_CTRL_IRQ_ENABLE, dev->mmio + HW_PXP_CTRL_SET); + writel(BM_PXP_CTRL_ENABLE | BM_PXP_CTRL_ENABLE_CSC2 | + BM_PXP_CTRL_ENABLE_LUT | BM_PXP_CTRL_ENABLE_ROTATE0 | + BM_PXP_CTRL_ENABLE_PS_AS_OUT, dev->mmio + HW_PXP_CTRL_SET); + + return 0; +} + +static void pxp_job_finish(struct pxp_dev *dev) +{ + struct pxp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + + curr_ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + spin_lock_irqsave(&dev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&dev->irqlock, flags); + + dprintk(curr_ctx->dev, "Finishing transaction\n"); + v4l2_m2m_job_finish(dev->m2m_dev, curr_ctx->fh.m2m_ctx); +} + +/* + * mem2mem callbacks + */ +static void pxp_device_run(void *priv) +{ + struct pxp_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + pxp_start(ctx, src_buf, dst_buf); +} + +static int pxp_job_ready(void *priv) +{ + struct pxp_ctx *ctx = priv; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) < 1 || + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) < 1) { + dprintk(ctx->dev, "Not enough buffers available\n"); + return 0; + } + + return 1; +} + +static void pxp_job_abort(void *priv) +{ + struct pxp_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +/* + * interrupt handler + */ +static irqreturn_t pxp_irq_handler(int irq, void *dev_id) +{ + struct pxp_dev *dev = dev_id; + u32 stat; + + stat = readl(dev->mmio + HW_PXP_STAT); + + if (stat & BM_PXP_STAT_IRQ0) { + /* we expect x = 0, y = height, irq0 = 1 */ + if (stat & ~(BM_PXP_STAT_BLOCKX | BM_PXP_STAT_BLOCKY | + BM_PXP_STAT_IRQ0)) + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + writel(BM_PXP_STAT_IRQ0, dev->mmio + HW_PXP_STAT_CLR); + + pxp_job_finish(dev); + } else { + u32 irq = readl(dev->mmio + HW_PXP_IRQ); + + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + dprintk(dev, "%s: irq = 0x%08x\n", __func__, irq); + + writel(irq, dev->mmio + HW_PXP_IRQ_CLR); + } + + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int pxp_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", MEM2MEM_NAME); + return 0; +} + +static int pxp_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct pxp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* + * Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int pxp_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int pxp_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int pxp_g_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct pxp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.bytesperline = q_data->bytesperline; + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = q_data->ycbcr_enc; + f->fmt.pix.quantization = q_data->quant; + + return 0; +} + +static int pxp_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static int pxp_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static inline u32 pxp_bytesperline(struct pxp_fmt *fmt, u32 width) +{ + switch (fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + return width; + default: + return (width * fmt->depth) >> 3; + } +} + +static inline u32 pxp_sizeimage(struct pxp_fmt *fmt, u32 width, u32 height) +{ + return (fmt->depth * width * height) >> 3; +} + +static int pxp_try_fmt(struct v4l2_format *f, struct pxp_fmt *fmt) +{ + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, ALIGN_W, + &f->fmt.pix.height, MIN_H, MAX_H, ALIGN_H, 0); + + f->fmt.pix.bytesperline = pxp_bytesperline(fmt, f->fmt.pix.width); + f->fmt.pix.sizeimage = pxp_sizeimage(fmt, f->fmt.pix.width, + f->fmt.pix.height); + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static void +pxp_fixup_colorimetry_cap(struct pxp_ctx *ctx, u32 dst_fourcc, + enum v4l2_ycbcr_encoding *ycbcr_enc, + enum v4l2_quantization *quantization) +{ + bool dst_is_yuv = pxp_v4l2_pix_fmt_is_yuv(dst_fourcc); + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) == + dst_is_yuv) { + /* + * There is no support for conversion between different YCbCr + * encodings or between RGB limited and full range. + */ + *ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + *quantization = ctx->q_data[V4L2_M2M_SRC].quant; + } else { + *ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(ctx->colorspace); + *quantization = V4L2_MAP_QUANTIZATION_DEFAULT(!dst_is_yuv, + ctx->colorspace, + *ycbcr_enc); + } +} + +static int pxp_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f); + } + if (!(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + + pxp_fixup_colorimetry_cap(ctx, fmt->fourcc, + &f->fmt.pix.ycbcr_enc, + &f->fmt.pix.quantization); + + return pxp_try_fmt(f, fmt); +} + +static int pxp_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f); + } + if (!(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + return pxp_try_fmt(f, fmt); +} + +static int pxp_s_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct pxp_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->bytesperline = f->fmt.pix.bytesperline; + q_data->sizeimage = f->fmt.pix.sizeimage; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int pxp_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->q_data[V4L2_M2M_DST].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_DST].quant = f->fmt.pix.quantization; + + return 0; +} + +static int pxp_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->q_data[V4L2_M2M_SRC].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_SRC].quant = f->fmt.pix.quantization; + + pxp_fixup_colorimetry_cap(ctx, ctx->q_data[V4L2_M2M_DST].fmt->fourcc, + &ctx->q_data[V4L2_M2M_DST].ycbcr_enc, + &ctx->q_data[V4L2_M2M_DST].quant); + + return 0; +} + +static u8 pxp_degrees_to_rot_mode(u32 degrees) +{ + switch (degrees) { + case 90: + return BV_PXP_CTRL_ROTATE0__ROT_90; + case 180: + return BV_PXP_CTRL_ROTATE0__ROT_180; + case 270: + return BV_PXP_CTRL_ROTATE0__ROT_270; + case 0: + default: + return BV_PXP_CTRL_ROTATE0__ROT_0; + } +} + +static int pxp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct pxp_ctx *ctx = + container_of(ctrl->handler, struct pxp_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_HFLIP; + else + ctx->mode &= ~MEM2MEM_HFLIP; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_VFLIP; + else + ctx->mode &= ~MEM2MEM_VFLIP; + break; + + case V4L2_CID_ROTATE: + ctx->rotation = pxp_degrees_to_rot_mode(ctrl->val); + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->alpha_component = ctrl->val; + break; + + default: + v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops pxp_ctrl_ops = { + .s_ctrl = pxp_s_ctrl, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_enum_fmt_vid_cap = pxp_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = pxp_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = pxp_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = pxp_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = pxp_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = pxp_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = pxp_s_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_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Queue operations + */ +static int pxp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(vq); + struct pxp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + size = q_data->sizeimage; + + *nbuffers = count; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int pxp_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dprintk(dev, "%s field isn't supported\n", __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void pxp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int pxp_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct pxp_q_data *q_data = get_q_data(ctx, q->type); + + q_data->sequence = 0; + return 0; +} + +static void pxp_stop_streaming(struct vb2_queue *q) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + unsigned long flags; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vbuf == NULL) + return; + spin_lock_irqsave(&ctx->dev->irqlock, flags); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->dev->irqlock, flags); + } +} + +static const struct vb2_ops pxp_qops = { + .queue_setup = pxp_queue_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .start_streaming = pxp_start_streaming, + .stop_streaming = pxp_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct pxp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &pxp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = ctx->dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &pxp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int pxp_open(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, + 0, 255, 1, 255); + if (hdl->error) { + rc = hdl->error; + v4l2_ctrl_handler_free(hdl); + kfree(ctx); + goto open_unlock; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[0]; + ctx->q_data[V4L2_M2M_SRC].width = 640; + ctx->q_data[V4L2_M2M_SRC].height = 480; + ctx->q_data[V4L2_M2M_SRC].bytesperline = + pxp_bytesperline(&formats[0], 640); + ctx->q_data[V4L2_M2M_SRC].sizeimage = + pxp_sizeimage(&formats[0], 640, 480); + ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; + ctx->colorspace = V4L2_COLORSPACE_REC709; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh); + atomic_inc(&dev->num_inst); + + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + +open_unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int pxp_release(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = file2ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->dev_mutex); + kfree(ctx); + + atomic_dec(&dev->num_inst); + + return 0; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device pxp_videodev = { + .name = MEM2MEM_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &pxp_fops, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, + .ioctl_ops = &pxp_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = pxp_device_run, + .job_ready = pxp_job_ready, + .job_abort = pxp_job_abort, +}; + +static int pxp_soft_reset(struct pxp_dev *dev) +{ + int ret; + u32 val; + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + + ret = readl_poll_timeout(dev->mmio + HW_PXP_CTRL, val, + val & BM_PXP_CTRL_CLKGATE, 0, 100); + if (ret < 0) + return ret; + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + + return 0; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxp_dev *dev; + struct video_device *vfd; + int irq; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + dev_err(&pdev->dev, "Failed to get clk: %d\n", ret); + return ret; + } + + dev->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->mmio)) + return PTR_ERR(dev->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + spin_lock_init(&dev->irqlock); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, pxp_irq_handler, + IRQF_ONESHOT, dev_name(&pdev->dev), dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + + ret = pxp_soft_reset(dev); + if (ret < 0) { + dev_err(&pdev->dev, "PXP reset timeout: %d\n", ret); + goto err_clk; + } + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto err_clk; + + atomic_set(&dev->num_inst, 0); + mutex_init(&dev->dev_mutex); + + dev->vfd = pxp_videodev; + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", pxp_videodev.name); + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto err_v4l2; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_m2m; + } + + return 0; + +err_m2m: + v4l2_m2m_release(dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); +err_clk: + clk_disable_unprepare(dev->clk); + + return ret; +} + +static int pxp_remove(struct platform_device *pdev) +{ + struct pxp_dev *dev = platform_get_drvdata(pdev); + + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_SET); + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + + clk_disable_unprepare(dev->clk); + + v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME); + video_unregister_device(&dev->vfd); + v4l2_m2m_release(dev->m2m_dev); + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static const struct of_device_id pxp_dt_ids[] = { + { .compatible = "fsl,imx6ull-pxp", .data = NULL }, + { }, +}; +MODULE_DEVICE_TABLE(of, pxp_dt_ids); + +static struct platform_driver pxp_driver = { + .probe = pxp_probe, + .remove = pxp_remove, + .driver = { + .name = MEM2MEM_NAME, + .of_match_table = pxp_dt_ids, + }, +}; + +module_platform_driver(pxp_driver); + +MODULE_DESCRIPTION("i.MX PXP mem2mem scaler/CSC/rotator"); +MODULE_AUTHOR("Philipp Zabel <kernel@pengutronix.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/imx-pxp.h b/drivers/media/platform/nxp/imx-pxp.h new file mode 100644 index 000000000000..44f95c749d2e --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.h @@ -0,0 +1,1685 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Freescale PXP Register Definitions + * + * based on pxp_dma_v3.h, Xml Revision: 1.77, Template Revision: 1.3 + * + * Copyright 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#ifndef __IMX_PXP_H__ +#define __IMX_PXP_H__ + +#define HW_PXP_CTRL (0x00000000) +#define HW_PXP_CTRL_SET (0x00000004) +#define HW_PXP_CTRL_CLR (0x00000008) +#define HW_PXP_CTRL_TOG (0x0000000c) + +#define BM_PXP_CTRL_SFTRST 0x80000000 +#define BF_PXP_CTRL_SFTRST(v) \ + (((v) << 31) & BM_PXP_CTRL_SFTRST) +#define BM_PXP_CTRL_CLKGATE 0x40000000 +#define BF_PXP_CTRL_CLKGATE(v) \ + (((v) << 30) & BM_PXP_CTRL_CLKGATE) +#define BM_PXP_CTRL_RSVD4 0x20000000 +#define BF_PXP_CTRL_RSVD4(v) \ + (((v) << 29) & BM_PXP_CTRL_RSVD4) +#define BM_PXP_CTRL_EN_REPEAT 0x10000000 +#define BF_PXP_CTRL_EN_REPEAT(v) \ + (((v) << 28) & BM_PXP_CTRL_EN_REPEAT) +#define BM_PXP_CTRL_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL_ENABLE_ROTATE1) +#define BM_PXP_CTRL_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL_ENABLE_ROTATE0) +#define BM_PXP_CTRL_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL_ENABLE_LUT) +#define BM_PXP_CTRL_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL_ENABLE_CSC2) +#define BM_PXP_CTRL_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL_BLOCK_SIZE) +#define BV_PXP_CTRL_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL_RSVD1 0x00400000 +#define BF_PXP_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_CTRL_RSVD1) +#define BM_PXP_CTRL_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL_ENABLE_ALPHA_B) +#define BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL_ENABLE_WFE_B) +#define BM_PXP_CTRL_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL_ENABLE_WFE_A) +#define BM_PXP_CTRL_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL_ENABLE_DITHER) +#define BM_PXP_CTRL_ENABLE_PS_AS_OUT 0x00010000 +#define BF_PXP_CTRL_ENABLE_PS_AS_OUT(v) \ + (((v) << 16) & BM_PXP_CTRL_ENABLE_PS_AS_OUT) +#define BM_PXP_CTRL_VFLIP1 0x00008000 +#define BF_PXP_CTRL_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL_VFLIP1) +#define BM_PXP_CTRL_HFLIP1 0x00004000 +#define BF_PXP_CTRL_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL_HFLIP1) +#define BP_PXP_CTRL_ROTATE1 12 +#define BM_PXP_CTRL_ROTATE1 0x00003000 +#define BF_PXP_CTRL_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL_ROTATE1) +#define BV_PXP_CTRL_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL_VFLIP0 0x00000800 +#define BF_PXP_CTRL_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL_VFLIP0) +#define BM_PXP_CTRL_HFLIP0 0x00000400 +#define BF_PXP_CTRL_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL_HFLIP0) +#define BP_PXP_CTRL_ROTATE0 8 +#define BM_PXP_CTRL_ROTATE0 0x00000300 +#define BF_PXP_CTRL_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL_ROTATE0) +#define BV_PXP_CTRL_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL_RSVD0 6 +#define BM_PXP_CTRL_RSVD0 0x000000C0 +#define BF_PXP_CTRL_RSVD0(v) \ + (((v) << 6) & BM_PXP_CTRL_RSVD0) +#define BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP 0x00000020 +#define BF_PXP_CTRL_HANDSHAKE_ABORT_SKIP(v) \ + (((v) << 5) & BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP) +#define BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE 0x00000010 +#define BF_PXP_CTRL_ENABLE_LCD0_HANDSHAKE(v) \ + (((v) << 4) & BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE) +#define BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE 0x00000008 +#define BF_PXP_CTRL_LUT_DMA_IRQ_ENABLE(v) \ + (((v) << 3) & BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE) +#define BM_PXP_CTRL_NEXT_IRQ_ENABLE 0x00000004 +#define BF_PXP_CTRL_NEXT_IRQ_ENABLE(v) \ + (((v) << 2) & BM_PXP_CTRL_NEXT_IRQ_ENABLE) +#define BM_PXP_CTRL_IRQ_ENABLE 0x00000002 +#define BF_PXP_CTRL_IRQ_ENABLE(v) \ + (((v) << 1) & BM_PXP_CTRL_IRQ_ENABLE) +#define BM_PXP_CTRL_ENABLE 0x00000001 +#define BF_PXP_CTRL_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL_ENABLE) + +#define HW_PXP_STAT (0x00000010) +#define HW_PXP_STAT_SET (0x00000014) +#define HW_PXP_STAT_CLR (0x00000018) +#define HW_PXP_STAT_TOG (0x0000001c) + +#define BP_PXP_STAT_BLOCKX 24 +#define BM_PXP_STAT_BLOCKX 0xFF000000 +#define BF_PXP_STAT_BLOCKX(v) \ + (((v) << 24) & BM_PXP_STAT_BLOCKX) +#define BP_PXP_STAT_BLOCKY 16 +#define BM_PXP_STAT_BLOCKY 0x00FF0000 +#define BF_PXP_STAT_BLOCKY(v) \ + (((v) << 16) & BM_PXP_STAT_BLOCKY) +#define BP_PXP_STAT_AXI_ERROR_ID_1 12 +#define BM_PXP_STAT_AXI_ERROR_ID_1 0x0000F000 +#define BF_PXP_STAT_AXI_ERROR_ID_1(v) \ + (((v) << 12) & BM_PXP_STAT_AXI_ERROR_ID_1) +#define BM_PXP_STAT_RSVD2 0x00000800 +#define BF_PXP_STAT_RSVD2(v) \ + (((v) << 11) & BM_PXP_STAT_RSVD2) +#define BM_PXP_STAT_AXI_READ_ERROR_1 0x00000400 +#define BF_PXP_STAT_AXI_READ_ERROR_1(v) \ + (((v) << 10) & BM_PXP_STAT_AXI_READ_ERROR_1) +#define BM_PXP_STAT_AXI_WRITE_ERROR_1 0x00000200 +#define BF_PXP_STAT_AXI_WRITE_ERROR_1(v) \ + (((v) << 9) & BM_PXP_STAT_AXI_WRITE_ERROR_1) +#define BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ 0x00000100 +#define BF_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ(v) \ + (((v) << 8) & BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ) +#define BP_PXP_STAT_AXI_ERROR_ID_0 4 +#define BM_PXP_STAT_AXI_ERROR_ID_0 0x000000F0 +#define BF_PXP_STAT_AXI_ERROR_ID_0(v) \ + (((v) << 4) & BM_PXP_STAT_AXI_ERROR_ID_0) +#define BM_PXP_STAT_NEXT_IRQ 0x00000008 +#define BF_PXP_STAT_NEXT_IRQ(v) \ + (((v) << 3) & BM_PXP_STAT_NEXT_IRQ) +#define BM_PXP_STAT_AXI_READ_ERROR_0 0x00000004 +#define BF_PXP_STAT_AXI_READ_ERROR_0(v) \ + (((v) << 2) & BM_PXP_STAT_AXI_READ_ERROR_0) +#define BM_PXP_STAT_AXI_WRITE_ERROR_0 0x00000002 +#define BF_PXP_STAT_AXI_WRITE_ERROR_0(v) \ + (((v) << 1) & BM_PXP_STAT_AXI_WRITE_ERROR_0) +#define BM_PXP_STAT_IRQ0 0x00000001 +#define BF_PXP_STAT_IRQ0(v) \ + (((v) << 0) & BM_PXP_STAT_IRQ0) + +#define HW_PXP_OUT_CTRL (0x00000020) +#define HW_PXP_OUT_CTRL_SET (0x00000024) +#define HW_PXP_OUT_CTRL_CLR (0x00000028) +#define HW_PXP_OUT_CTRL_TOG (0x0000002c) + +#define BP_PXP_OUT_CTRL_ALPHA 24 +#define BM_PXP_OUT_CTRL_ALPHA 0xFF000000 +#define BF_PXP_OUT_CTRL_ALPHA(v) \ + (((v) << 24) & BM_PXP_OUT_CTRL_ALPHA) +#define BM_PXP_OUT_CTRL_ALPHA_OUTPUT 0x00800000 +#define BF_PXP_OUT_CTRL_ALPHA_OUTPUT(v) \ + (((v) << 23) & BM_PXP_OUT_CTRL_ALPHA_OUTPUT) +#define BP_PXP_OUT_CTRL_RSVD1 10 +#define BM_PXP_OUT_CTRL_RSVD1 0x007FFC00 +#define BF_PXP_OUT_CTRL_RSVD1(v) \ + (((v) << 10) & BM_PXP_OUT_CTRL_RSVD1) +#define BP_PXP_OUT_CTRL_INTERLACED_OUTPUT 8 +#define BM_PXP_OUT_CTRL_INTERLACED_OUTPUT 0x00000300 +#define BF_PXP_OUT_CTRL_INTERLACED_OUTPUT(v) \ + (((v) << 8) & BM_PXP_OUT_CTRL_INTERLACED_OUTPUT) +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__PROGRESSIVE 0x0 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD0 0x1 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD1 0x2 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__INTERLACED 0x3 +#define BP_PXP_OUT_CTRL_RSVD0 5 +#define BM_PXP_OUT_CTRL_RSVD0 0x000000E0 +#define BF_PXP_OUT_CTRL_RSVD0(v) \ + (((v) << 5) & BM_PXP_OUT_CTRL_RSVD0) +#define BP_PXP_OUT_CTRL_FORMAT 0 +#define BM_PXP_OUT_CTRL_FORMAT 0x0000001F +#define BF_PXP_OUT_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_OUT_CTRL_FORMAT) +#define BV_PXP_OUT_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888P 0x5 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_OUT_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_OUT_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_OUT_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_OUT_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_OUT_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_OUT_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_OUT_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_OUT_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P420 0x1B + +#define HW_PXP_OUT_BUF (0x00000030) + +#define BP_PXP_OUT_BUF_ADDR 0 +#define BM_PXP_OUT_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF_ADDR(v) (v) + +#define HW_PXP_OUT_BUF2 (0x00000040) + +#define BP_PXP_OUT_BUF2_ADDR 0 +#define BM_PXP_OUT_BUF2_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF2_ADDR(v) (v) + +#define HW_PXP_OUT_PITCH (0x00000050) + +#define BP_PXP_OUT_PITCH_RSVD 16 +#define BM_PXP_OUT_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_OUT_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_OUT_PITCH_RSVD) +#define BP_PXP_OUT_PITCH_PITCH 0 +#define BM_PXP_OUT_PITCH_PITCH 0x0000FFFF +#define BF_PXP_OUT_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_OUT_PITCH_PITCH) + +#define HW_PXP_OUT_LRC (0x00000060) + +#define BP_PXP_OUT_LRC_RSVD1 30 +#define BM_PXP_OUT_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_LRC_RSVD1) +#define BP_PXP_OUT_LRC_X 16 +#define BM_PXP_OUT_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_LRC_X) +#define BP_PXP_OUT_LRC_RSVD0 14 +#define BM_PXP_OUT_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_LRC_RSVD0) +#define BP_PXP_OUT_LRC_Y 0 +#define BM_PXP_OUT_LRC_Y 0x00003FFF +#define BF_PXP_OUT_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_LRC_Y) + +#define HW_PXP_OUT_PS_ULC (0x00000070) + +#define BP_PXP_OUT_PS_ULC_RSVD1 30 +#define BM_PXP_OUT_PS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_ULC_RSVD1) +#define BP_PXP_OUT_PS_ULC_X 16 +#define BM_PXP_OUT_PS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_ULC_X) +#define BP_PXP_OUT_PS_ULC_RSVD0 14 +#define BM_PXP_OUT_PS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_ULC_RSVD0) +#define BP_PXP_OUT_PS_ULC_Y 0 +#define BM_PXP_OUT_PS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_PS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_ULC_Y) + +#define HW_PXP_OUT_PS_LRC (0x00000080) + +#define BP_PXP_OUT_PS_LRC_RSVD1 30 +#define BM_PXP_OUT_PS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_LRC_RSVD1) +#define BP_PXP_OUT_PS_LRC_X 16 +#define BM_PXP_OUT_PS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_LRC_X) +#define BP_PXP_OUT_PS_LRC_RSVD0 14 +#define BM_PXP_OUT_PS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_LRC_RSVD0) +#define BP_PXP_OUT_PS_LRC_Y 0 +#define BM_PXP_OUT_PS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_PS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_LRC_Y) + +#define HW_PXP_OUT_AS_ULC (0x00000090) + +#define BP_PXP_OUT_AS_ULC_RSVD1 30 +#define BM_PXP_OUT_AS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_ULC_RSVD1) +#define BP_PXP_OUT_AS_ULC_X 16 +#define BM_PXP_OUT_AS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_ULC_X) +#define BP_PXP_OUT_AS_ULC_RSVD0 14 +#define BM_PXP_OUT_AS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_ULC_RSVD0) +#define BP_PXP_OUT_AS_ULC_Y 0 +#define BM_PXP_OUT_AS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_AS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_ULC_Y) + +#define HW_PXP_OUT_AS_LRC (0x000000a0) + +#define BP_PXP_OUT_AS_LRC_RSVD1 30 +#define BM_PXP_OUT_AS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_LRC_RSVD1) +#define BP_PXP_OUT_AS_LRC_X 16 +#define BM_PXP_OUT_AS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_LRC_X) +#define BP_PXP_OUT_AS_LRC_RSVD0 14 +#define BM_PXP_OUT_AS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_LRC_RSVD0) +#define BP_PXP_OUT_AS_LRC_Y 0 +#define BM_PXP_OUT_AS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_AS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_LRC_Y) + +#define HW_PXP_PS_CTRL (0x000000b0) +#define HW_PXP_PS_CTRL_SET (0x000000b4) +#define HW_PXP_PS_CTRL_CLR (0x000000b8) +#define HW_PXP_PS_CTRL_TOG (0x000000bc) + +#define BP_PXP_PS_CTRL_RSVD1 12 +#define BM_PXP_PS_CTRL_RSVD1 0xFFFFF000 +#define BF_PXP_PS_CTRL_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_CTRL_RSVD1) +#define BP_PXP_PS_CTRL_DECX 10 +#define BM_PXP_PS_CTRL_DECX 0x00000C00 +#define BF_PXP_PS_CTRL_DECX(v) \ + (((v) << 10) & BM_PXP_PS_CTRL_DECX) +#define BV_PXP_PS_CTRL_DECX__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECX__DECX2 0x1 +#define BV_PXP_PS_CTRL_DECX__DECX4 0x2 +#define BV_PXP_PS_CTRL_DECX__DECX8 0x3 +#define BP_PXP_PS_CTRL_DECY 8 +#define BM_PXP_PS_CTRL_DECY 0x00000300 +#define BF_PXP_PS_CTRL_DECY(v) \ + (((v) << 8) & BM_PXP_PS_CTRL_DECY) +#define BV_PXP_PS_CTRL_DECY__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECY__DECY2 0x1 +#define BV_PXP_PS_CTRL_DECY__DECY4 0x2 +#define BV_PXP_PS_CTRL_DECY__DECY8 0x3 +#define BM_PXP_PS_CTRL_RSVD0 0x00000080 +#define BF_PXP_PS_CTRL_RSVD0(v) \ + (((v) << 7) & BM_PXP_PS_CTRL_RSVD0) +#define BM_PXP_PS_CTRL_WB_SWAP 0x00000040 +#define BF_PXP_PS_CTRL_WB_SWAP(v) \ + (((v) << 6) & BM_PXP_PS_CTRL_WB_SWAP) +#define BP_PXP_PS_CTRL_FORMAT 0 +#define BM_PXP_PS_CTRL_FORMAT 0x0000003F +#define BF_PXP_PS_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_PS_CTRL_FORMAT) +#define BV_PXP_PS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_PS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_PS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_PS_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_PS_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_PS_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_PS_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_PS_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_PS_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_PS_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_PS_CTRL_FORMAT__YVU2P420 0x1B +#define BV_PXP_PS_CTRL_FORMAT__YUV422 0x1E +#define BV_PXP_PS_CTRL_FORMAT__YUV420 0x1F + +#define HW_PXP_PS_BUF (0x000000c0) + +#define BP_PXP_PS_BUF_ADDR 0 +#define BM_PXP_PS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_BUF_ADDR(v) (v) + +#define HW_PXP_PS_UBUF (0x000000d0) + +#define BP_PXP_PS_UBUF_ADDR 0 +#define BM_PXP_PS_UBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_UBUF_ADDR(v) (v) + +#define HW_PXP_PS_VBUF (0x000000e0) + +#define BP_PXP_PS_VBUF_ADDR 0 +#define BM_PXP_PS_VBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_VBUF_ADDR(v) (v) + +#define HW_PXP_PS_PITCH (0x000000f0) + +#define BP_PXP_PS_PITCH_RSVD 16 +#define BM_PXP_PS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_PS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_PS_PITCH_RSVD) +#define BP_PXP_PS_PITCH_PITCH 0 +#define BM_PXP_PS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_PS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_PS_PITCH_PITCH) + +#define HW_PXP_PS_BACKGROUND_0 (0x00000100) + +#define BP_PXP_PS_BACKGROUND_0_RSVD 24 +#define BM_PXP_PS_BACKGROUND_0_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_0_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_0_RSVD) +#define BP_PXP_PS_BACKGROUND_0_COLOR 0 +#define BM_PXP_PS_BACKGROUND_0_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_0_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_0_COLOR) + +#define HW_PXP_PS_SCALE (0x00000110) + +#define BM_PXP_PS_SCALE_RSVD2 0x80000000 +#define BF_PXP_PS_SCALE_RSVD2(v) \ + (((v) << 31) & BM_PXP_PS_SCALE_RSVD2) +#define BP_PXP_PS_SCALE_YSCALE 16 +#define BM_PXP_PS_SCALE_YSCALE 0x7FFF0000 +#define BF_PXP_PS_SCALE_YSCALE(v) \ + (((v) << 16) & BM_PXP_PS_SCALE_YSCALE) +#define BM_PXP_PS_SCALE_RSVD1 0x00008000 +#define BF_PXP_PS_SCALE_RSVD1(v) \ + (((v) << 15) & BM_PXP_PS_SCALE_RSVD1) +#define BP_PXP_PS_SCALE_XSCALE 0 +#define BM_PXP_PS_SCALE_XSCALE 0x00007FFF +#define BF_PXP_PS_SCALE_XSCALE(v) \ + (((v) << 0) & BM_PXP_PS_SCALE_XSCALE) + +#define HW_PXP_PS_OFFSET (0x00000120) + +#define BP_PXP_PS_OFFSET_RSVD2 28 +#define BM_PXP_PS_OFFSET_RSVD2 0xF0000000 +#define BF_PXP_PS_OFFSET_RSVD2(v) \ + (((v) << 28) & BM_PXP_PS_OFFSET_RSVD2) +#define BP_PXP_PS_OFFSET_YOFFSET 16 +#define BM_PXP_PS_OFFSET_YOFFSET 0x0FFF0000 +#define BF_PXP_PS_OFFSET_YOFFSET(v) \ + (((v) << 16) & BM_PXP_PS_OFFSET_YOFFSET) +#define BP_PXP_PS_OFFSET_RSVD1 12 +#define BM_PXP_PS_OFFSET_RSVD1 0x0000F000 +#define BF_PXP_PS_OFFSET_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_OFFSET_RSVD1) +#define BP_PXP_PS_OFFSET_XOFFSET 0 +#define BM_PXP_PS_OFFSET_XOFFSET 0x00000FFF +#define BF_PXP_PS_OFFSET_XOFFSET(v) \ + (((v) << 0) & BM_PXP_PS_OFFSET_XOFFSET) + +#define HW_PXP_PS_CLRKEYLOW_0 (0x00000130) + +#define BP_PXP_PS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_0 (0x00000140) + +#define BP_PXP_PS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_AS_CTRL (0x00000150) + +#define BP_PXP_AS_CTRL_RSVD1 22 +#define BM_PXP_AS_CTRL_RSVD1 0xFFC00000 +#define BF_PXP_AS_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_AS_CTRL_RSVD1) +#define BM_PXP_AS_CTRL_ALPHA1_INVERT 0x00200000 +#define BF_PXP_AS_CTRL_ALPHA1_INVERT(v) \ + (((v) << 21) & BM_PXP_AS_CTRL_ALPHA1_INVERT) +#define BM_PXP_AS_CTRL_ALPHA0_INVERT 0x00100000 +#define BF_PXP_AS_CTRL_ALPHA0_INVERT(v) \ + (((v) << 20) & BM_PXP_AS_CTRL_ALPHA0_INVERT) +#define BP_PXP_AS_CTRL_ROP 16 +#define BM_PXP_AS_CTRL_ROP 0x000F0000 +#define BF_PXP_AS_CTRL_ROP(v) \ + (((v) << 16) & BM_PXP_AS_CTRL_ROP) +#define BV_PXP_AS_CTRL_ROP__MASKAS 0x0 +#define BV_PXP_AS_CTRL_ROP__MASKNOTAS 0x1 +#define BV_PXP_AS_CTRL_ROP__MASKASNOT 0x2 +#define BV_PXP_AS_CTRL_ROP__MERGEAS 0x3 +#define BV_PXP_AS_CTRL_ROP__MERGENOTAS 0x4 +#define BV_PXP_AS_CTRL_ROP__MERGEASNOT 0x5 +#define BV_PXP_AS_CTRL_ROP__NOTCOPYAS 0x6 +#define BV_PXP_AS_CTRL_ROP__NOT 0x7 +#define BV_PXP_AS_CTRL_ROP__NOTMASKAS 0x8 +#define BV_PXP_AS_CTRL_ROP__NOTMERGEAS 0x9 +#define BV_PXP_AS_CTRL_ROP__XORAS 0xA +#define BV_PXP_AS_CTRL_ROP__NOTXORAS 0xB +#define BP_PXP_AS_CTRL_ALPHA 8 +#define BM_PXP_AS_CTRL_ALPHA 0x0000FF00 +#define BF_PXP_AS_CTRL_ALPHA(v) \ + (((v) << 8) & BM_PXP_AS_CTRL_ALPHA) +#define BP_PXP_AS_CTRL_FORMAT 4 +#define BM_PXP_AS_CTRL_FORMAT 0x000000F0 +#define BF_PXP_AS_CTRL_FORMAT(v) \ + (((v) << 4) & BM_PXP_AS_CTRL_FORMAT) +#define BV_PXP_AS_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_AS_CTRL_FORMAT__RGBA8888 0x1 +#define BV_PXP_AS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_AS_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_AS_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_AS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_AS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_AS_CTRL_FORMAT__RGB565 0xE +#define BM_PXP_AS_CTRL_ENABLE_COLORKEY 0x00000008 +#define BF_PXP_AS_CTRL_ENABLE_COLORKEY(v) \ + (((v) << 3) & BM_PXP_AS_CTRL_ENABLE_COLORKEY) +#define BP_PXP_AS_CTRL_ALPHA_CTRL 1 +#define BM_PXP_AS_CTRL_ALPHA_CTRL 0x00000006 +#define BF_PXP_AS_CTRL_ALPHA_CTRL(v) \ + (((v) << 1) & BM_PXP_AS_CTRL_ALPHA_CTRL) +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Embedded 0x0 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Override 0x1 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Multiply 0x2 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__ROPs 0x3 +#define BM_PXP_AS_CTRL_RSVD0 0x00000001 +#define BF_PXP_AS_CTRL_RSVD0(v) \ + (((v) << 0) & BM_PXP_AS_CTRL_RSVD0) + +#define HW_PXP_AS_BUF (0x00000160) + +#define BP_PXP_AS_BUF_ADDR 0 +#define BM_PXP_AS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_AS_BUF_ADDR(v) (v) + +#define HW_PXP_AS_PITCH (0x00000170) + +#define BP_PXP_AS_PITCH_RSVD 16 +#define BM_PXP_AS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_AS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_AS_PITCH_RSVD) +#define BP_PXP_AS_PITCH_PITCH 0 +#define BM_PXP_AS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_AS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_AS_PITCH_PITCH) + +#define HW_PXP_AS_CLRKEYLOW_0 (0x00000180) + +#define BP_PXP_AS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_0 (0x00000190) + +#define BP_PXP_AS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_CSC1_COEF0 (0x000001a0) + +#define BM_PXP_CSC1_COEF0_YCBCR_MODE 0x80000000 +#define BF_PXP_CSC1_COEF0_YCBCR_MODE(v) \ + (((v) << 31) & BM_PXP_CSC1_COEF0_YCBCR_MODE) +#define BM_PXP_CSC1_COEF0_BYPASS 0x40000000 +#define BF_PXP_CSC1_COEF0_BYPASS(v) \ + (((v) << 30) & BM_PXP_CSC1_COEF0_BYPASS) +#define BM_PXP_CSC1_COEF0_RSVD1 0x20000000 +#define BF_PXP_CSC1_COEF0_RSVD1(v) \ + (((v) << 29) & BM_PXP_CSC1_COEF0_RSVD1) +#define BP_PXP_CSC1_COEF0_C0 18 +#define BM_PXP_CSC1_COEF0_C0 0x1FFC0000 +#define BF_PXP_CSC1_COEF0_C0(v) \ + (((v) << 18) & BM_PXP_CSC1_COEF0_C0) +#define BP_PXP_CSC1_COEF0_UV_OFFSET 9 +#define BM_PXP_CSC1_COEF0_UV_OFFSET 0x0003FE00 +#define BF_PXP_CSC1_COEF0_UV_OFFSET(v) \ + (((v) << 9) & BM_PXP_CSC1_COEF0_UV_OFFSET) +#define BP_PXP_CSC1_COEF0_Y_OFFSET 0 +#define BM_PXP_CSC1_COEF0_Y_OFFSET 0x000001FF +#define BF_PXP_CSC1_COEF0_Y_OFFSET(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF0_Y_OFFSET) + +#define HW_PXP_CSC1_COEF1 (0x000001b0) + +#define BP_PXP_CSC1_COEF1_RSVD1 27 +#define BM_PXP_CSC1_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF1_RSVD1) +#define BP_PXP_CSC1_COEF1_C1 16 +#define BM_PXP_CSC1_COEF1_C1 0x07FF0000 +#define BF_PXP_CSC1_COEF1_C1(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF1_C1) +#define BP_PXP_CSC1_COEF1_RSVD0 11 +#define BM_PXP_CSC1_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF1_RSVD0) +#define BP_PXP_CSC1_COEF1_C4 0 +#define BM_PXP_CSC1_COEF1_C4 0x000007FF +#define BF_PXP_CSC1_COEF1_C4(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF1_C4) + +#define HW_PXP_CSC1_COEF2 (0x000001c0) + +#define BP_PXP_CSC1_COEF2_RSVD1 27 +#define BM_PXP_CSC1_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF2_RSVD1) +#define BP_PXP_CSC1_COEF2_C2 16 +#define BM_PXP_CSC1_COEF2_C2 0x07FF0000 +#define BF_PXP_CSC1_COEF2_C2(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF2_C2) +#define BP_PXP_CSC1_COEF2_RSVD0 11 +#define BM_PXP_CSC1_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF2_RSVD0) +#define BP_PXP_CSC1_COEF2_C3 0 +#define BM_PXP_CSC1_COEF2_C3 0x000007FF +#define BF_PXP_CSC1_COEF2_C3(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF2_C3) + +#define HW_PXP_CSC2_CTRL (0x000001d0) + +#define BP_PXP_CSC2_CTRL_RSVD 3 +#define BM_PXP_CSC2_CTRL_RSVD 0xFFFFFFF8 +#define BF_PXP_CSC2_CTRL_RSVD(v) \ + (((v) << 3) & BM_PXP_CSC2_CTRL_RSVD) +#define BP_PXP_CSC2_CTRL_CSC_MODE 1 +#define BM_PXP_CSC2_CTRL_CSC_MODE 0x00000006 +#define BF_PXP_CSC2_CTRL_CSC_MODE(v) \ + (((v) << 1) & BM_PXP_CSC2_CTRL_CSC_MODE) +#define BV_PXP_CSC2_CTRL_CSC_MODE__YUV2RGB 0x0 +#define BV_PXP_CSC2_CTRL_CSC_MODE__YCbCr2RGB 0x1 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV 0x2 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr 0x3 +#define BM_PXP_CSC2_CTRL_BYPASS 0x00000001 +#define BF_PXP_CSC2_CTRL_BYPASS(v) \ + (((v) << 0) & BM_PXP_CSC2_CTRL_BYPASS) + +#define HW_PXP_CSC2_COEF0 (0x000001e0) + +#define BP_PXP_CSC2_COEF0_RSVD1 27 +#define BM_PXP_CSC2_COEF0_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF0_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF0_RSVD1) +#define BP_PXP_CSC2_COEF0_A2 16 +#define BM_PXP_CSC2_COEF0_A2 0x07FF0000 +#define BF_PXP_CSC2_COEF0_A2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF0_A2) +#define BP_PXP_CSC2_COEF0_RSVD0 11 +#define BM_PXP_CSC2_COEF0_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF0_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF0_RSVD0) +#define BP_PXP_CSC2_COEF0_A1 0 +#define BM_PXP_CSC2_COEF0_A1 0x000007FF +#define BF_PXP_CSC2_COEF0_A1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF0_A1) + +#define HW_PXP_CSC2_COEF1 (0x000001f0) + +#define BP_PXP_CSC2_COEF1_RSVD1 27 +#define BM_PXP_CSC2_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF1_RSVD1) +#define BP_PXP_CSC2_COEF1_B1 16 +#define BM_PXP_CSC2_COEF1_B1 0x07FF0000 +#define BF_PXP_CSC2_COEF1_B1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF1_B1) +#define BP_PXP_CSC2_COEF1_RSVD0 11 +#define BM_PXP_CSC2_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF1_RSVD0) +#define BP_PXP_CSC2_COEF1_A3 0 +#define BM_PXP_CSC2_COEF1_A3 0x000007FF +#define BF_PXP_CSC2_COEF1_A3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF1_A3) + +#define HW_PXP_CSC2_COEF2 (0x00000200) + +#define BP_PXP_CSC2_COEF2_RSVD1 27 +#define BM_PXP_CSC2_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF2_RSVD1) +#define BP_PXP_CSC2_COEF2_B3 16 +#define BM_PXP_CSC2_COEF2_B3 0x07FF0000 +#define BF_PXP_CSC2_COEF2_B3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF2_B3) +#define BP_PXP_CSC2_COEF2_RSVD0 11 +#define BM_PXP_CSC2_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF2_RSVD0) +#define BP_PXP_CSC2_COEF2_B2 0 +#define BM_PXP_CSC2_COEF2_B2 0x000007FF +#define BF_PXP_CSC2_COEF2_B2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF2_B2) + +#define HW_PXP_CSC2_COEF3 (0x00000210) + +#define BP_PXP_CSC2_COEF3_RSVD1 27 +#define BM_PXP_CSC2_COEF3_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF3_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF3_RSVD1) +#define BP_PXP_CSC2_COEF3_C2 16 +#define BM_PXP_CSC2_COEF3_C2 0x07FF0000 +#define BF_PXP_CSC2_COEF3_C2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF3_C2) +#define BP_PXP_CSC2_COEF3_RSVD0 11 +#define BM_PXP_CSC2_COEF3_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF3_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF3_RSVD0) +#define BP_PXP_CSC2_COEF3_C1 0 +#define BM_PXP_CSC2_COEF3_C1 0x000007FF +#define BF_PXP_CSC2_COEF3_C1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF3_C1) + +#define HW_PXP_CSC2_COEF4 (0x00000220) + +#define BP_PXP_CSC2_COEF4_RSVD1 25 +#define BM_PXP_CSC2_COEF4_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF4_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF4_RSVD1) +#define BP_PXP_CSC2_COEF4_D1 16 +#define BM_PXP_CSC2_COEF4_D1 0x01FF0000 +#define BF_PXP_CSC2_COEF4_D1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF4_D1) +#define BP_PXP_CSC2_COEF4_RSVD0 11 +#define BM_PXP_CSC2_COEF4_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF4_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF4_RSVD0) +#define BP_PXP_CSC2_COEF4_C3 0 +#define BM_PXP_CSC2_COEF4_C3 0x000007FF +#define BF_PXP_CSC2_COEF4_C3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF4_C3) + +#define HW_PXP_CSC2_COEF5 (0x00000230) + +#define BP_PXP_CSC2_COEF5_RSVD1 25 +#define BM_PXP_CSC2_COEF5_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF5_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF5_RSVD1) +#define BP_PXP_CSC2_COEF5_D3 16 +#define BM_PXP_CSC2_COEF5_D3 0x01FF0000 +#define BF_PXP_CSC2_COEF5_D3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF5_D3) +#define BP_PXP_CSC2_COEF5_RSVD0 9 +#define BM_PXP_CSC2_COEF5_RSVD0 0x0000FE00 +#define BF_PXP_CSC2_COEF5_RSVD0(v) \ + (((v) << 9) & BM_PXP_CSC2_COEF5_RSVD0) +#define BP_PXP_CSC2_COEF5_D2 0 +#define BM_PXP_CSC2_COEF5_D2 0x000001FF +#define BF_PXP_CSC2_COEF5_D2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF5_D2) + +#define HW_PXP_LUT_CTRL (0x00000240) + +#define BM_PXP_LUT_CTRL_BYPASS 0x80000000 +#define BF_PXP_LUT_CTRL_BYPASS(v) \ + (((v) << 31) & BM_PXP_LUT_CTRL_BYPASS) +#define BP_PXP_LUT_CTRL_RSVD3 26 +#define BM_PXP_LUT_CTRL_RSVD3 0x7C000000 +#define BF_PXP_LUT_CTRL_RSVD3(v) \ + (((v) << 26) & BM_PXP_LUT_CTRL_RSVD3) +#define BP_PXP_LUT_CTRL_LOOKUP_MODE 24 +#define BM_PXP_LUT_CTRL_LOOKUP_MODE 0x03000000 +#define BF_PXP_LUT_CTRL_LOOKUP_MODE(v) \ + (((v) << 24) & BM_PXP_LUT_CTRL_LOOKUP_MODE) +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__CACHE_RGB565 0x0 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8 0x1 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB444 0x2 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB454 0x3 +#define BP_PXP_LUT_CTRL_RSVD2 18 +#define BM_PXP_LUT_CTRL_RSVD2 0x00FC0000 +#define BF_PXP_LUT_CTRL_RSVD2(v) \ + (((v) << 18) & BM_PXP_LUT_CTRL_RSVD2) +#define BP_PXP_LUT_CTRL_OUT_MODE 16 +#define BM_PXP_LUT_CTRL_OUT_MODE 0x00030000 +#define BF_PXP_LUT_CTRL_OUT_MODE(v) \ + (((v) << 16) & BM_PXP_LUT_CTRL_OUT_MODE) +#define BV_PXP_LUT_CTRL_OUT_MODE__RESERVED 0x0 +#define BV_PXP_LUT_CTRL_OUT_MODE__Y8 0x1 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGBW4444CFA 0x2 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGB888 0x3 +#define BP_PXP_LUT_CTRL_RSVD1 11 +#define BM_PXP_LUT_CTRL_RSVD1 0x0000F800 +#define BF_PXP_LUT_CTRL_RSVD1(v) \ + (((v) << 11) & BM_PXP_LUT_CTRL_RSVD1) +#define BM_PXP_LUT_CTRL_SEL_8KB 0x00000400 +#define BF_PXP_LUT_CTRL_SEL_8KB(v) \ + (((v) << 10) & BM_PXP_LUT_CTRL_SEL_8KB) +#define BM_PXP_LUT_CTRL_LRU_UPD 0x00000200 +#define BF_PXP_LUT_CTRL_LRU_UPD(v) \ + (((v) << 9) & BM_PXP_LUT_CTRL_LRU_UPD) +#define BM_PXP_LUT_CTRL_INVALID 0x00000100 +#define BF_PXP_LUT_CTRL_INVALID(v) \ + (((v) << 8) & BM_PXP_LUT_CTRL_INVALID) +#define BP_PXP_LUT_CTRL_RSVD0 1 +#define BM_PXP_LUT_CTRL_RSVD0 0x000000FE +#define BF_PXP_LUT_CTRL_RSVD0(v) \ + (((v) << 1) & BM_PXP_LUT_CTRL_RSVD0) +#define BM_PXP_LUT_CTRL_DMA_START 0x00000001 +#define BF_PXP_LUT_CTRL_DMA_START(v) \ + (((v) << 0) & BM_PXP_LUT_CTRL_DMA_START) + +#define HW_PXP_LUT_ADDR (0x00000250) + +#define BM_PXP_LUT_ADDR_RSVD2 0x80000000 +#define BF_PXP_LUT_ADDR_RSVD2(v) \ + (((v) << 31) & BM_PXP_LUT_ADDR_RSVD2) +#define BP_PXP_LUT_ADDR_NUM_BYTES 16 +#define BM_PXP_LUT_ADDR_NUM_BYTES 0x7FFF0000 +#define BF_PXP_LUT_ADDR_NUM_BYTES(v) \ + (((v) << 16) & BM_PXP_LUT_ADDR_NUM_BYTES) +#define BP_PXP_LUT_ADDR_RSVD1 14 +#define BM_PXP_LUT_ADDR_RSVD1 0x0000C000 +#define BF_PXP_LUT_ADDR_RSVD1(v) \ + (((v) << 14) & BM_PXP_LUT_ADDR_RSVD1) +#define BP_PXP_LUT_ADDR_ADDR 0 +#define BM_PXP_LUT_ADDR_ADDR 0x00003FFF +#define BF_PXP_LUT_ADDR_ADDR(v) \ + (((v) << 0) & BM_PXP_LUT_ADDR_ADDR) + +#define HW_PXP_LUT_DATA (0x00000260) + +#define BP_PXP_LUT_DATA_DATA 0 +#define BM_PXP_LUT_DATA_DATA 0xFFFFFFFF +#define BF_PXP_LUT_DATA_DATA(v) (v) + +#define HW_PXP_LUT_EXTMEM (0x00000270) + +#define BP_PXP_LUT_EXTMEM_ADDR 0 +#define BM_PXP_LUT_EXTMEM_ADDR 0xFFFFFFFF +#define BF_PXP_LUT_EXTMEM_ADDR(v) (v) + +#define HW_PXP_CFA (0x00000280) + +#define BP_PXP_CFA_DATA 0 +#define BM_PXP_CFA_DATA 0xFFFFFFFF +#define BF_PXP_CFA_DATA(v) (v) + +#define HW_PXP_ALPHA_A_CTRL (0x00000290) + +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_A_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_A_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_A_CTRL_RSVD0) +#define BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_A_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x0 +#define BP_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_A_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_A_CTRL_RSVD1) +#define BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_A_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL (0x000002a0) + +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_B_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_B_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_B_CTRL_RSVD0) +#define BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_B_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_B_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_B_CTRL_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_B_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL_1 (0x000002b0) + +#define BP_PXP_ALPHA_B_CTRL_1_RSVD0 8 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD0 0xFFFFFF00 +#define BF_PXP_ALPHA_B_CTRL_1_RSVD0(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_1_RSVD0) +#define BP_PXP_ALPHA_B_CTRL_1_ROP 4 +#define BM_PXP_ALPHA_B_CTRL_1_ROP 0x000000F0 +#define BF_PXP_ALPHA_B_CTRL_1_ROP(v) \ + (((v) << 4) & BM_PXP_ALPHA_B_CTRL_1_ROP) +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKAS 0x0 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKNOTAS 0x1 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKASNOT 0x2 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEAS 0x3 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGENOTAS 0x4 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEASNOT 0x5 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTCOPYAS 0x6 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOT 0x7 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMASKAS 0x8 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMERGEAS 0x9 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__XORAS 0xA +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTXORAS 0xB +#define BP_PXP_ALPHA_B_CTRL_1_RSVD1 2 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD1 0x0000000C +#define BF_PXP_ALPHA_B_CTRL_1_RSVD1(v) \ + (((v) << 2) & BM_PXP_ALPHA_B_CTRL_1_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE 0x00000002 +#define BF_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE) +#define BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_1_ROP_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE) + +#define HW_PXP_PS_BACKGROUND_1 (0x000002c0) + +#define BP_PXP_PS_BACKGROUND_1_RSVD 24 +#define BM_PXP_PS_BACKGROUND_1_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_1_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_1_RSVD) +#define BP_PXP_PS_BACKGROUND_1_COLOR 0 +#define BM_PXP_PS_BACKGROUND_1_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_1_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_1_COLOR) + +#define HW_PXP_PS_CLRKEYLOW_1 (0x000002d0) + +#define BP_PXP_PS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_1 (0x000002e0) + +#define BP_PXP_PS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_AS_CLRKEYLOW_1 (0x000002f0) + +#define BP_PXP_AS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_1 (0x00000300) + +#define BP_PXP_AS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_CTRL2 (0x00000310) +#define HW_PXP_CTRL2_SET (0x00000314) +#define HW_PXP_CTRL2_CLR (0x00000318) +#define HW_PXP_CTRL2_TOG (0x0000031c) + +#define BP_PXP_CTRL2_RSVD3 28 +#define BM_PXP_CTRL2_RSVD3 0xF0000000 +#define BF_PXP_CTRL2_RSVD3(v) \ + (((v) << 28) & BM_PXP_CTRL2_RSVD3) +#define BM_PXP_CTRL2_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL2_ENABLE_ROTATE1) +#define BM_PXP_CTRL2_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL2_ENABLE_ROTATE0) +#define BM_PXP_CTRL2_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL2_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL2_ENABLE_LUT) +#define BM_PXP_CTRL2_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL2_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL2_ENABLE_CSC2) +#define BM_PXP_CTRL2_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL2_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL2_BLOCK_SIZE) +#define BV_PXP_CTRL2_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL2_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL2_RSVD2 0x00400000 +#define BF_PXP_CTRL2_RSVD2(v) \ + (((v) << 22) & BM_PXP_CTRL2_RSVD2) +#define BM_PXP_CTRL2_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL2_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL2_ENABLE_ALPHA_B) +#define BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL2_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL2_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL2_ENABLE_WFE_B) +#define BM_PXP_CTRL2_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL2_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL2_ENABLE_WFE_A) +#define BM_PXP_CTRL2_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL2_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL2_ENABLE_DITHER) +#define BM_PXP_CTRL2_RSVD1 0x00010000 +#define BF_PXP_CTRL2_RSVD1(v) \ + (((v) << 16) & BM_PXP_CTRL2_RSVD1) +#define BM_PXP_CTRL2_VFLIP1 0x00008000 +#define BF_PXP_CTRL2_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL2_VFLIP1) +#define BM_PXP_CTRL2_HFLIP1 0x00004000 +#define BF_PXP_CTRL2_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL2_HFLIP1) +#define BP_PXP_CTRL2_ROTATE1 12 +#define BM_PXP_CTRL2_ROTATE1 0x00003000 +#define BF_PXP_CTRL2_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL2_ROTATE1) +#define BV_PXP_CTRL2_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL2_VFLIP0 0x00000800 +#define BF_PXP_CTRL2_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL2_VFLIP0) +#define BM_PXP_CTRL2_HFLIP0 0x00000400 +#define BF_PXP_CTRL2_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL2_HFLIP0) +#define BP_PXP_CTRL2_ROTATE0 8 +#define BM_PXP_CTRL2_ROTATE0 0x00000300 +#define BF_PXP_CTRL2_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL2_ROTATE0) +#define BV_PXP_CTRL2_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL2_RSVD0 1 +#define BM_PXP_CTRL2_RSVD0 0x000000FE +#define BF_PXP_CTRL2_RSVD0(v) \ + (((v) << 1) & BM_PXP_CTRL2_RSVD0) +#define BM_PXP_CTRL2_ENABLE 0x00000001 +#define BF_PXP_CTRL2_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL2_ENABLE) + +#define HW_PXP_POWER_REG0 (0x00000320) + +#define BP_PXP_POWER_REG0_CTRL 12 +#define BM_PXP_POWER_REG0_CTRL 0xFFFFF000 +#define BF_PXP_POWER_REG0_CTRL(v) \ + (((v) << 12) & BM_PXP_POWER_REG0_CTRL) +#define BP_PXP_POWER_REG0_ROT0_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG0_ROT0_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE) +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 6 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 0x000001C0 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN(v) \ + (((v) << 6) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 3 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 0x00000038 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN(v) \ + (((v) << 3) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0x00000007 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0(v) \ + (((v) << 0) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__SD 0x4 + +#define HW_PXP_POWER_REG1 (0x00000330) + +#define BP_PXP_POWER_REG1_RSVD0 24 +#define BM_PXP_POWER_REG1_RSVD0 0xFF000000 +#define BF_PXP_POWER_REG1_RSVD0(v) \ + (((v) << 24) & BM_PXP_POWER_REG1_RSVD0) +#define BP_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 21 +#define BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 0x00E00000 +#define BF_PXP_POWER_REG1_ALU_B_MEM_LP_STATE(v) \ + (((v) << 21) & BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 18 +#define BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 0x001C0000 +#define BF_PXP_POWER_REG1_ALU_A_MEM_LP_STATE(v) \ + (((v) << 18) & BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 15 +#define BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 0x00038000 +#define BF_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE(v) \ + (((v) << 15) & BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 12 +#define BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 0x00007000 +#define BF_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE(v) \ + (((v) << 12) & BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 6 +#define BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 0x000001C0 +#define BF_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE(v) \ + (((v) << 6) & BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 3 +#define BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 0x00000038 +#define BF_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE(v) \ + (((v) << 3) & BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0 +#define BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0x00000007 +#define BF_PXP_POWER_REG1_ROT1_MEM_LP_STATE(v) \ + (((v) << 0) & BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__SD 0x4 + +#define HW_PXP_DATA_PATH_CTRL0 (0x00000340) +#define HW_PXP_DATA_PATH_CTRL0_SET (0x00000344) +#define HW_PXP_DATA_PATH_CTRL0_CLR (0x00000348) +#define HW_PXP_DATA_PATH_CTRL0_TOG (0x0000034c) + +#define BP_PXP_DATA_PATH_CTRL0_MUX15_SEL 30 +#define BM_PXP_DATA_PATH_CTRL0_MUX15_SEL 0xC0000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(v) \ + (((v) << 30) & BM_PXP_DATA_PATH_CTRL0_MUX15_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX14_SEL 28 +#define BM_PXP_DATA_PATH_CTRL0_MUX14_SEL 0x30000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(v) \ + (((v) << 28) & BM_PXP_DATA_PATH_CTRL0_MUX14_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX13_SEL 26 +#define BM_PXP_DATA_PATH_CTRL0_MUX13_SEL 0x0C000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(v) \ + (((v) << 26) & BM_PXP_DATA_PATH_CTRL0_MUX13_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX12_SEL 24 +#define BM_PXP_DATA_PATH_CTRL0_MUX12_SEL 0x03000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(v) \ + (((v) << 24) & BM_PXP_DATA_PATH_CTRL0_MUX12_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX11_SEL 22 +#define BM_PXP_DATA_PATH_CTRL0_MUX11_SEL 0x00C00000 +#define BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(v) \ + (((v) << 22) & BM_PXP_DATA_PATH_CTRL0_MUX11_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX10_SEL 20 +#define BM_PXP_DATA_PATH_CTRL0_MUX10_SEL 0x00300000 +#define BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(v) \ + (((v) << 20) & BM_PXP_DATA_PATH_CTRL0_MUX10_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX9_SEL 18 +#define BM_PXP_DATA_PATH_CTRL0_MUX9_SEL 0x000C0000 +#define BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(v) \ + (((v) << 18) & BM_PXP_DATA_PATH_CTRL0_MUX9_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX8_SEL 16 +#define BM_PXP_DATA_PATH_CTRL0_MUX8_SEL 0x00030000 +#define BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(v) \ + (((v) << 16) & BM_PXP_DATA_PATH_CTRL0_MUX8_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX7_SEL 14 +#define BM_PXP_DATA_PATH_CTRL0_MUX7_SEL 0x0000C000 +#define BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(v) \ + (((v) << 14) & BM_PXP_DATA_PATH_CTRL0_MUX7_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX6_SEL 12 +#define BM_PXP_DATA_PATH_CTRL0_MUX6_SEL 0x00003000 +#define BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(v) \ + (((v) << 12) & BM_PXP_DATA_PATH_CTRL0_MUX6_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX5_SEL 10 +#define BM_PXP_DATA_PATH_CTRL0_MUX5_SEL 0x00000C00 +#define BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(v) \ + (((v) << 10) & BM_PXP_DATA_PATH_CTRL0_MUX5_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX4_SEL 8 +#define BM_PXP_DATA_PATH_CTRL0_MUX4_SEL 0x00000300 +#define BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(v) \ + (((v) << 8) & BM_PXP_DATA_PATH_CTRL0_MUX4_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX3_SEL 6 +#define BM_PXP_DATA_PATH_CTRL0_MUX3_SEL 0x000000C0 +#define BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(v) \ + (((v) << 6) & BM_PXP_DATA_PATH_CTRL0_MUX3_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX2_SEL 4 +#define BM_PXP_DATA_PATH_CTRL0_MUX2_SEL 0x00000030 +#define BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL0_MUX2_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX1_SEL 2 +#define BM_PXP_DATA_PATH_CTRL0_MUX1_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL0_MUX1_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX0_SEL 0 +#define BM_PXP_DATA_PATH_CTRL0_MUX0_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL0_MUX0_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__3 0x3 + +#define HW_PXP_DATA_PATH_CTRL1 (0x00000350) +#define HW_PXP_DATA_PATH_CTRL1_SET (0x00000354) +#define HW_PXP_DATA_PATH_CTRL1_CLR (0x00000358) +#define HW_PXP_DATA_PATH_CTRL1_TOG (0x0000035c) + +#define BP_PXP_DATA_PATH_CTRL1_RSVD0 4 +#define BM_PXP_DATA_PATH_CTRL1_RSVD0 0xFFFFFFF0 +#define BF_PXP_DATA_PATH_CTRL1_RSVD0(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL1_RSVD0) +#define BP_PXP_DATA_PATH_CTRL1_MUX17_SEL 2 +#define BM_PXP_DATA_PATH_CTRL1_MUX17_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL1_MUX17_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL1_MUX16_SEL 0 +#define BM_PXP_DATA_PATH_CTRL1_MUX16_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL1_MUX16_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__3 0x3 + +#define HW_PXP_INIT_MEM_CTRL (0x00000360) +#define HW_PXP_INIT_MEM_CTRL_SET (0x00000364) +#define HW_PXP_INIT_MEM_CTRL_CLR (0x00000368) +#define HW_PXP_INIT_MEM_CTRL_TOG (0x0000036c) + +#define BM_PXP_INIT_MEM_CTRL_START 0x80000000 +#define BF_PXP_INIT_MEM_CTRL_START(v) \ + (((v) << 31) & BM_PXP_INIT_MEM_CTRL_START) +#define BP_PXP_INIT_MEM_CTRL_SELECT 27 +#define BM_PXP_INIT_MEM_CTRL_SELECT 0x78000000 +#define BF_PXP_INIT_MEM_CTRL_SELECT(v) \ + (((v) << 27) & BM_PXP_INIT_MEM_CTRL_SELECT) +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_LUT 0x0 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR0 0x1 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR1 0x2 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER1_LUT 0x3 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER2_LUT 0x4 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_A 0x5 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_B 0x6 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_A_FETCH 0x7 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_B_FETCH 0x8 +#define BV_PXP_INIT_MEM_CTRL_SELECT__RESERVED 0x15 +#define BP_PXP_INIT_MEM_CTRL_RSVD0 16 +#define BM_PXP_INIT_MEM_CTRL_RSVD0 0x07FF0000 +#define BF_PXP_INIT_MEM_CTRL_RSVD0(v) \ + (((v) << 16) & BM_PXP_INIT_MEM_CTRL_RSVD0) +#define BP_PXP_INIT_MEM_CTRL_ADDR 0 +#define BM_PXP_INIT_MEM_CTRL_ADDR 0x0000FFFF +#define BF_PXP_INIT_MEM_CTRL_ADDR(v) \ + (((v) << 0) & BM_PXP_INIT_MEM_CTRL_ADDR) + +#define HW_PXP_INIT_MEM_DATA (0x00000370) + +#define BP_PXP_INIT_MEM_DATA_DATA 0 +#define BM_PXP_INIT_MEM_DATA_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_DATA(v) (v) + +#define HW_PXP_INIT_MEM_DATA_HIGH (0x00000380) + +#define BP_PXP_INIT_MEM_DATA_HIGH_DATA 0 +#define BM_PXP_INIT_MEM_DATA_HIGH_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_HIGH_DATA(v) (v) + +#define HW_PXP_IRQ_MASK (0x00000390) +#define HW_PXP_IRQ_MASK_SET (0x00000394) +#define HW_PXP_IRQ_MASK_CLR (0x00000398) +#define HW_PXP_IRQ_MASK_TOG (0x0000039c) + +#define BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN 0x80000000 +#define BF_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN(v) \ + (((v) << 31) & BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN) +#define BP_PXP_IRQ_MASK_RSVD1 16 +#define BM_PXP_IRQ_MASK_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_MASK_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_MASK_RSVD1) +#define BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN 0x00008000 +#define BF_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN(v) \ + (((v) << 15) & BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN 0x00004000 +#define BF_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN(v) \ + (((v) << 14) & BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN 0x00002000 +#define BF_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN(v) \ + (((v) << 13) & BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN 0x00001000 +#define BF_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN(v) \ + (((v) << 12) & BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN 0x00000800 +#define BF_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN(v) \ + (((v) << 11) & BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN 0x00000400 +#define BF_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN(v) \ + (((v) << 10) & BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN 0x00000200 +#define BF_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN(v) \ + (((v) << 9) & BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN 0x00000100 +#define BF_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN(v) \ + (((v) << 8) & BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN 0x00000080 +#define BF_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN(v) \ + (((v) << 7) & BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN 0x00000040 +#define BF_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN(v) \ + (((v) << 6) & BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN 0x00000020 +#define BF_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 5) & BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN 0x00000010 +#define BF_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 4) & BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN 0x00000008 +#define BF_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN(v) \ + (((v) << 3) & BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN 0x00000004 +#define BF_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN(v) \ + (((v) << 2) & BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN 0x00000002 +#define BF_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 1) & BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN 0x00000001 +#define BF_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 0) & BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN) + +#define HW_PXP_IRQ (0x000003a0) +#define HW_PXP_IRQ_SET (0x000003a4) +#define HW_PXP_IRQ_CLR (0x000003a8) +#define HW_PXP_IRQ_TOG (0x000003ac) + +#define BM_PXP_IRQ_COMPRESS_DONE_IRQ 0x80000000 +#define BF_PXP_IRQ_COMPRESS_DONE_IRQ(v) \ + (((v) << 31) & BM_PXP_IRQ_COMPRESS_DONE_IRQ) +#define BP_PXP_IRQ_RSVD1 16 +#define BM_PXP_IRQ_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_RSVD1) +#define BM_PXP_IRQ_WFE_B_STORE_IRQ 0x00008000 +#define BF_PXP_IRQ_WFE_B_STORE_IRQ(v) \ + (((v) << 15) & BM_PXP_IRQ_WFE_B_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_STORE_IRQ 0x00004000 +#define BF_PXP_IRQ_WFE_A_STORE_IRQ(v) \ + (((v) << 14) & BM_PXP_IRQ_WFE_A_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_STORE_IRQ 0x00002000 +#define BF_PXP_IRQ_DITHER_STORE_IRQ(v) \ + (((v) << 13) & BM_PXP_IRQ_DITHER_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_STORE_IRQ 0x00001000 +#define BF_PXP_IRQ_FIRST_STORE_IRQ(v) \ + (((v) << 12) & BM_PXP_IRQ_FIRST_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ 0x00000800 +#define BF_PXP_IRQ_WFE_B_CH1_STORE_IRQ(v) \ + (((v) << 11) & BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ 0x00000400 +#define BF_PXP_IRQ_WFE_B_CH0_STORE_IRQ(v) \ + (((v) << 10) & BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ 0x00000200 +#define BF_PXP_IRQ_WFE_A_CH1_STORE_IRQ(v) \ + (((v) << 9) & BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ 0x00000100 +#define BF_PXP_IRQ_WFE_A_CH0_STORE_IRQ(v) \ + (((v) << 8) & BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_STORE_IRQ 0x00000080 +#define BF_PXP_IRQ_DITHER_CH1_STORE_IRQ(v) \ + (((v) << 7) & BM_PXP_IRQ_DITHER_CH1_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_STORE_IRQ 0x00000040 +#define BF_PXP_IRQ_DITHER_CH0_STORE_IRQ(v) \ + (((v) << 6) & BM_PXP_IRQ_DITHER_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ 0x00000020 +#define BF_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ(v) \ + (((v) << 5) & BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ 0x00000010 +#define BF_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ(v) \ + (((v) << 4) & BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_STORE_IRQ 0x00000008 +#define BF_PXP_IRQ_FIRST_CH1_STORE_IRQ(v) \ + (((v) << 3) & BM_PXP_IRQ_FIRST_CH1_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_STORE_IRQ 0x00000004 +#define BF_PXP_IRQ_FIRST_CH0_STORE_IRQ(v) \ + (((v) << 2) & BM_PXP_IRQ_FIRST_CH0_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ 0x00000002 +#define BF_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ(v) \ + (((v) << 1) & BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ 0x00000001 +#define BF_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ(v) \ + (((v) << 0) & BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ) + +#define HW_PXP_NEXT (0x00000400) + +#define BP_PXP_NEXT_POINTER 2 +#define BM_PXP_NEXT_POINTER 0xFFFFFFFC +#define BF_PXP_NEXT_POINTER(v) \ + (((v) << 2) & BM_PXP_NEXT_POINTER) +#define BM_PXP_NEXT_RSVD 0x00000002 +#define BF_PXP_NEXT_RSVD(v) \ + (((v) << 1) & BM_PXP_NEXT_RSVD) +#define BM_PXP_NEXT_ENABLED 0x00000001 +#define BF_PXP_NEXT_ENABLED(v) \ + (((v) << 0) & BM_PXP_NEXT_ENABLED) + +#define HW_PXP_DEBUGCTRL (0x00000410) + +#define BP_PXP_DEBUGCTRL_RSVD 12 +#define BM_PXP_DEBUGCTRL_RSVD 0xFFFFF000 +#define BF_PXP_DEBUGCTRL_RSVD(v) \ + (((v) << 12) & BM_PXP_DEBUGCTRL_RSVD) +#define BP_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 8 +#define BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 0x00000F00 +#define BF_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT(v) \ + (((v) << 8) & BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT) +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MISS_CNT 0x1 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__HIT_CNT 0x2 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__LAT_CNT 0x4 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MAX_LAT 0x8 +#define BP_PXP_DEBUGCTRL_SELECT 0 +#define BM_PXP_DEBUGCTRL_SELECT 0x000000FF +#define BF_PXP_DEBUGCTRL_SELECT(v) \ + (((v) << 0) & BM_PXP_DEBUGCTRL_SELECT) +#define BV_PXP_DEBUGCTRL_SELECT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_SELECT__CTRL 0x1 +#define BV_PXP_DEBUGCTRL_SELECT__PSBUF 0x2 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAX 0x3 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAY 0x4 +#define BV_PXP_DEBUGCTRL_SELECT__ASBUF 0x5 +#define BV_PXP_DEBUGCTRL_SELECT__ROTATION 0x6 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF0 0x7 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF1 0x8 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF2 0x9 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_STAT 0x10 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MISS 0x11 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_HIT 0x12 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_LAT 0x13 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MAX_LAT 0x14 + +#define HW_PXP_DEBUG (0x00000420) + +#define BP_PXP_DEBUG_DATA 0 +#define BM_PXP_DEBUG_DATA 0xFFFFFFFF +#define BF_PXP_DEBUG_DATA(v) (v) + +#define HW_PXP_VERSION (0x00000430) + +#define BP_PXP_VERSION_MAJOR 24 +#define BM_PXP_VERSION_MAJOR 0xFF000000 +#define BF_PXP_VERSION_MAJOR(v) \ + (((v) << 24) & BM_PXP_VERSION_MAJOR) +#define BP_PXP_VERSION_MINOR 16 +#define BM_PXP_VERSION_MINOR 0x00FF0000 +#define BF_PXP_VERSION_MINOR(v) \ + (((v) << 16) & BM_PXP_VERSION_MINOR) +#define BP_PXP_VERSION_STEP 0 +#define BM_PXP_VERSION_STEP 0x0000FFFF +#define BF_PXP_VERSION_STEP(v) \ + (((v) << 0) & BM_PXP_VERSION_STEP) + +#endif /* __IMX_PXP_H__ */ diff --git a/drivers/media/platform/nxp/mx2_emmaprp.c b/drivers/media/platform/nxp/mx2_emmaprp.c new file mode 100644 index 000000000000..3ce84d0f078c --- /dev/null +++ b/drivers/media/platform/nxp/mx2_emmaprp.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support eMMa-PrP through mem2mem framework. + * + * eMMa-PrP is a piece of HW that allows fetching buffers + * from one memory location and do several operations on + * them such as scaling or format conversion giving, as a result + * a new processed buffer in another memory location. + * + * Based on mem2mem_testdev.c by Pawel Osciak. + * + * Copyright (c) 2011 Vista Silicon S.L. + * Javier Martin <javier.martin@vista-silicon.com> + */ +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#include <linux/platform_device.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <linux/sizes.h> + +#define EMMAPRP_MODULE_NAME "mem2mem-emmaprp" + +MODULE_DESCRIPTION("Mem-to-mem device which supports eMMa-PrP present in mx2 SoCs"); +MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.0.1"); + +static bool debug; +module_param(debug, bool, 0644); + +#define MIN_W 32 +#define MIN_H 32 +#define MAX_W 2040 +#define MAX_H 2046 + +#define S_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN_YUV420 3 /* multiple of 8 */ +#define W_ALIGN_OTHERS 2 /* multiple of 4 */ +#define H_ALIGN 1 /* multiple of 2 */ + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "m2m-emmaprp" + +/* In bytes, per queue */ +#define MEM2MEM_VID_MEM_LIMIT SZ_16M + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1UL << 31) + +#define PRP_SIZE_HEIGHT(x) (x) +#define PRP_SIZE_WIDTH(x) ((x) << 16) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define PRP_INTR_ST_RDERR (1 << 0) +#define PRP_INTR_ST_CH1WERR (1 << 1) +#define PRP_INTR_ST_CH2WERR (1 << 2) +#define PRP_INTR_ST_CH2B2CI (1 << 3) +#define PRP_INTR_ST_CH2B1CI (1 << 4) +#define PRP_INTR_ST_CH1B2CI (1 << 5) +#define PRP_INTR_ST_CH1B1CI (1 << 6) +#define PRP_INTR_ST_LBOVF (1 << 7) +#define PRP_INTR_ST_CH2OVF (1 << 8) + +struct emmaprp_fmt { + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct emmaprp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_OUTPUT, + }, +}; + +/* Per-queue, driver-specific private data */ +struct emmaprp_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct emmaprp_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct emmaprp_fmt *find_format(struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct emmaprp_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + + void __iomem *base_emma; + struct clk *clk_emma_ahb, *clk_emma_ipg; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct emmaprp_ctx { + struct v4l2_fh fh; + struct emmaprp_dev *dev; + /* Abort requested by m2m */ + int aborting; + struct emmaprp_q_data q_data[2]; +}; + +static struct emmaprp_q_data *get_q_data(struct emmaprp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * mem2mem callbacks + */ +static void emmaprp_job_abort(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->fh.m2m_ctx); +} + +static inline void emmaprp_dump_regs(struct emmaprp_dev *pcdev) +{ + dprintk(pcdev, + "eMMa-PrP Registers:\n" + " SOURCE_Y_PTR = 0x%08X\n" + " SRC_FRAME_SIZE = 0x%08X\n" + " DEST_Y_PTR = 0x%08X\n" + " DEST_CR_PTR = 0x%08X\n" + " DEST_CB_PTR = 0x%08X\n" + " CH2_OUT_IMAGE_SIZE = 0x%08X\n" + " CNTL = 0x%08X\n", + readl(pcdev->base_emma + PRP_SOURCE_Y_PTR), + readl(pcdev->base_emma + PRP_SRC_FRAME_SIZE), + readl(pcdev->base_emma + PRP_DEST_Y_PTR), + readl(pcdev->base_emma + PRP_DEST_CR_PTR), + readl(pcdev->base_emma + PRP_DEST_CB_PTR), + readl(pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE), + readl(pcdev->base_emma + PRP_CNTL)); +} + +static void emmaprp_device_run(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_q_data *s_q_data, *d_q_data; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct emmaprp_dev *pcdev = ctx->dev; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size; + dma_addr_t p_in, p_out; + u32 tmp; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + + d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + /* Input frame parameters */ + writel(p_in, pcdev->base_emma + PRP_SOURCE_Y_PTR); + writel(PRP_SIZE_WIDTH(s_width) | PRP_SIZE_HEIGHT(s_height), + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + + /* Output frame parameters */ + writel(p_out, pcdev->base_emma + PRP_DEST_Y_PTR); + writel(p_out + d_size, pcdev->base_emma + PRP_DEST_CB_PTR); + writel(p_out + d_size + (d_size >> 2), + pcdev->base_emma + PRP_DEST_CR_PTR); + writel(PRP_SIZE_WIDTH(d_width) | PRP_SIZE_HEIGHT(d_height), + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + + /* IRQ configuration */ + tmp = readl(pcdev->base_emma + PRP_INTR_CNTL); + writel(tmp | PRP_INTR_RDERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH2FC, + pcdev->base_emma + PRP_INTR_CNTL); + + emmaprp_dump_regs(pcdev); + + /* Enable transfer */ + tmp = readl(pcdev->base_emma + PRP_CNTL); + writel(tmp | PRP_CNTL_CH2_OUT_YUV420 | + PRP_CNTL_DATA_IN_YUV422 | + PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); +} + +static irqreturn_t emmaprp_irq(int irq_emma, void *data) +{ + struct emmaprp_dev *pcdev = data; + struct emmaprp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + u32 irqst; + + /* Check irq flags and clear irq */ + irqst = readl(pcdev->base_emma + PRP_INTRSTATUS); + writel(irqst, pcdev->base_emma + PRP_INTRSTATUS); + dprintk(pcdev, "irqst = 0x%08x\n", irqst); + + curr_ctx = v4l2_m2m_get_curr_priv(pcdev->m2m_dev); + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (!curr_ctx->aborting) { + if ((irqst & PRP_INTR_ST_RDERR) || + (irqst & PRP_INTR_ST_CH2WERR)) { + pr_err("PrP bus error occurred, this transfer is probably corrupted\n"); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + } else if (irqst & PRP_INTR_ST_CH2B1CI) { /* buffer ready */ + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->flags &= + ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags + & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->timecode = src_vb->timecode; + + spin_lock_irqsave(&pcdev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&pcdev->irqlock, flags); + } + } + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct emmaprp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct emmaprp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + else /* YUYV */ + f->fmt.pix.bytesperline = q_data->width * 2; + f->fmt.pix.sizeimage = q_data->sizeimage; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f) +{ + enum v4l2_field field; + + + if (!find_format(f)) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_YUV420, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + } else { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_OTHERS, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_s_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct emmaprp_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(f); + if (ret) + return ret; + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + q_data->sizeimage = q_data->width * q_data->height * 3 / 2; + else /* YUYV */ + q_data->sizeimage = q_data->width * q_data->height * 2; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static const struct v4l2_ioctl_ops emmaprp_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_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_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + + +/* + * Queue operations + */ +static int emmaprp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vq); + struct emmaprp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + size = q_data->width * q_data->height * 3 / 2; + else + size = q_data->width * q_data->height * 2; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int emmaprp_buf_prepare(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct emmaprp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, + "%s data will not fit into plane(%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void emmaprp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops emmaprp_qops = { + .queue_setup = emmaprp_queue_setup, + .buf_prepare = emmaprp_buf_prepare, + .buf_queue = emmaprp_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct emmaprp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &emmaprp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = ctx->dev->v4l2_dev.dev; + src_vq->lock = &ctx->dev->dev_mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &emmaprp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + dst_vq->lock = &ctx->dev->dev_mutex; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int emmaprp_open(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = pcdev; + + if (mutex_lock_interruptible(&pcdev->dev_mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + int ret = PTR_ERR(ctx->fh.m2m_ctx); + + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + return ret; + } + + clk_prepare_enable(pcdev->clk_emma_ipg); + clk_prepare_enable(pcdev->clk_emma_ahb); + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[1]; + ctx->q_data[V4L2_M2M_DST].fmt = &formats[0]; + v4l2_fh_add(&ctx->fh); + mutex_unlock(&pcdev->dev_mutex); + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->fh.m2m_ctx); + + return 0; +} + +static int emmaprp_release(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + mutex_lock(&pcdev->dev_mutex); + clk_disable_unprepare(pcdev->clk_emma_ahb); + clk_disable_unprepare(pcdev->clk_emma_ipg); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations emmaprp_fops = { + .owner = THIS_MODULE, + .open = emmaprp_open, + .release = emmaprp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device emmaprp_videodev = { + .name = MEM2MEM_NAME, + .fops = &emmaprp_fops, + .ioctl_ops = &emmaprp_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = emmaprp_device_run, + .job_abort = emmaprp_job_abort, +}; + +static int emmaprp_probe(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev; + struct video_device *vfd; + int irq, ret; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + pcdev->clk_emma_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(pcdev->clk_emma_ipg)) { + return PTR_ERR(pcdev->clk_emma_ipg); + } + + pcdev->clk_emma_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(pcdev->clk_emma_ahb)) + return PTR_ERR(pcdev->clk_emma_ahb); + + pcdev->base_emma = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pcdev->base_emma)) + return PTR_ERR(pcdev->base_emma); + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + return ret; + + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = emmaprp_videodev; + vfd->lock = &pcdev->dev_mutex; + vfd->v4l2_dev = &pcdev->v4l2_dev; + + video_set_drvdata(vfd, pcdev); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, EMMAPRP_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto rel_vdev; + } + + ret = devm_request_irq(&pdev->dev, irq, emmaprp_irq, 0, + dev_name(&pdev->dev), pcdev); + if (ret) + goto rel_vdev; + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto rel_vdev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + return 0; + + +rel_m2m: + v4l2_m2m_release(pcdev->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); + + mutex_destroy(&pcdev->dev_mutex); + + return ret; +} + +static int emmaprp_remove(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " EMMAPRP_MODULE_NAME); + + video_unregister_device(pcdev->vfd); + v4l2_m2m_release(pcdev->m2m_dev); + v4l2_device_unregister(&pcdev->v4l2_dev); + mutex_destroy(&pcdev->dev_mutex); + + return 0; +} + +static struct platform_driver emmaprp_pdrv = { + .probe = emmaprp_probe, + .remove = emmaprp_remove, + .driver = { + .name = MEM2MEM_NAME, + }, +}; +module_platform_driver(emmaprp_pdrv); |