| /* |
| * TI VPE mem2mem driver, based on the virtual v4l2-mem2mem example driver |
| * |
| * Copyright (c) 2013 Texas Instruments Inc. |
| * David Griego, <dagriego@biglakesoftware.com> |
| * Dale Farnsworth, <dale@farnsworth.org> |
| * Archit Taneja, <archit@ti.com> |
| * |
| * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. |
| * Pawel Osciak, <pawel@osciak.com> |
| * Marek Szyprowski, <m.szyprowski@samsung.com> |
| * |
| * Based on the virtual v4l2-mem2mem example device |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published by |
| * the Free Software Foundation |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/err.h> |
| #include <linux/fs.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/ioctl.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/videodev2.h> |
| #include <linux/log2.h> |
| |
| #include <media/v4l2-common.h> |
| #include <media/v4l2-ctrls.h> |
| #include <media/v4l2-device.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-mem2mem.h> |
| #include <media/videobuf2-core.h> |
| #include <media/videobuf2-dma-contig.h> |
| |
| #include "vpdma.h" |
| #include "vpe_regs.h" |
| #include "sc.h" |
| #include "csc.h" |
| |
| #define VPE_MODULE_NAME "vpe" |
| |
| /* minimum and maximum frame sizes */ |
| #define MIN_W 32 |
| #define MIN_H 32 |
| #define MAX_W 1920 |
| #define MAX_H 1080 |
| |
| /* required alignments */ |
| #define S_ALIGN 0 /* multiple of 1 */ |
| #define H_ALIGN 1 /* multiple of 2 */ |
| |
| /* flags that indicate a format can be used for capture/output */ |
| #define VPE_FMT_TYPE_CAPTURE (1 << 0) |
| #define VPE_FMT_TYPE_OUTPUT (1 << 1) |
| |
| /* used as plane indices */ |
| #define VPE_MAX_PLANES 2 |
| #define VPE_LUMA 0 |
| #define VPE_CHROMA 1 |
| |
| /* per m2m context info */ |
| #define VPE_MAX_SRC_BUFS 3 /* need 3 src fields to de-interlace */ |
| |
| #define VPE_DEF_BUFS_PER_JOB 1 /* default one buffer per batch job */ |
| |
| /* |
| * each VPE context can need up to 3 config desciptors, 7 input descriptors, |
| * 3 output descriptors, and 10 control descriptors |
| */ |
| #define VPE_DESC_LIST_SIZE (10 * VPDMA_DTD_DESC_SIZE + \ |
| 13 * VPDMA_CFD_CTD_DESC_SIZE) |
| |
| #define vpe_dbg(vpedev, fmt, arg...) \ |
| dev_dbg((vpedev)->v4l2_dev.dev, fmt, ##arg) |
| #define vpe_err(vpedev, fmt, arg...) \ |
| dev_err((vpedev)->v4l2_dev.dev, fmt, ##arg) |
| |
| struct vpe_us_coeffs { |
| unsigned short anchor_fid0_c0; |
| unsigned short anchor_fid0_c1; |
| unsigned short anchor_fid0_c2; |
| unsigned short anchor_fid0_c3; |
| unsigned short interp_fid0_c0; |
| unsigned short interp_fid0_c1; |
| unsigned short interp_fid0_c2; |
| unsigned short interp_fid0_c3; |
| unsigned short anchor_fid1_c0; |
| unsigned short anchor_fid1_c1; |
| unsigned short anchor_fid1_c2; |
| unsigned short anchor_fid1_c3; |
| unsigned short interp_fid1_c0; |
| unsigned short interp_fid1_c1; |
| unsigned short interp_fid1_c2; |
| unsigned short interp_fid1_c3; |
| }; |
| |
| /* |
| * Default upsampler coefficients |
| */ |
| static const struct vpe_us_coeffs us_coeffs[] = { |
| { |
| /* Coefficients for progressive input */ |
| 0x00C8, 0x0348, 0x0018, 0x3FD8, 0x3FB8, 0x0378, 0x00E8, 0x3FE8, |
| 0x00C8, 0x0348, 0x0018, 0x3FD8, 0x3FB8, 0x0378, 0x00E8, 0x3FE8, |
| }, |
| { |
| /* Coefficients for Top Field Interlaced input */ |
| 0x0051, 0x03D5, 0x3FE3, 0x3FF7, 0x3FB5, 0x02E9, 0x018F, 0x3FD3, |
| /* Coefficients for Bottom Field Interlaced input */ |
| 0x016B, 0x0247, 0x00B1, 0x3F9D, 0x3FCF, 0x03DB, 0x005D, 0x3FF9, |
| }, |
| }; |
| |
| /* |
| * the following registers are for configuring some of the parameters of the |
| * motion and edge detection blocks inside DEI, these generally remain the same, |
| * these could be passed later via userspace if some one needs to tweak these. |
| */ |
| struct vpe_dei_regs { |
| unsigned long mdt_spacial_freq_thr_reg; /* VPE_DEI_REG2 */ |
| unsigned long edi_config_reg; /* VPE_DEI_REG3 */ |
| unsigned long edi_lut_reg0; /* VPE_DEI_REG4 */ |
| unsigned long edi_lut_reg1; /* VPE_DEI_REG5 */ |
| unsigned long edi_lut_reg2; /* VPE_DEI_REG6 */ |
| unsigned long edi_lut_reg3; /* VPE_DEI_REG7 */ |
| }; |
| |
| /* |
| * default expert DEI register values, unlikely to be modified. |
| */ |
| static const struct vpe_dei_regs dei_regs = { |
| 0x020C0804u, |
| 0x0118100Fu, |
| 0x08040200u, |
| 0x1010100Cu, |
| 0x10101010u, |
| 0x10101010u, |
| }; |
| |
| /* |
| * The port_data structure contains per-port data. |
| */ |
| struct vpe_port_data { |
| enum vpdma_channel channel; /* VPDMA channel */ |
| u8 vb_index; /* input frame f, f-1, f-2 index */ |
| u8 vb_part; /* plane index for co-panar formats */ |
| }; |
| |
| /* |
| * Define indices into the port_data tables |
| */ |
| #define VPE_PORT_LUMA1_IN 0 |
| #define VPE_PORT_CHROMA1_IN 1 |
| #define VPE_PORT_LUMA2_IN 2 |
| #define VPE_PORT_CHROMA2_IN 3 |
| #define VPE_PORT_LUMA3_IN 4 |
| #define VPE_PORT_CHROMA3_IN 5 |
| #define VPE_PORT_MV_IN 6 |
| #define VPE_PORT_MV_OUT 7 |
| #define VPE_PORT_LUMA_OUT 8 |
| #define VPE_PORT_CHROMA_OUT 9 |
| #define VPE_PORT_RGB_OUT 10 |
| |
| static const struct vpe_port_data port_data[11] = { |
| [VPE_PORT_LUMA1_IN] = { |
| .channel = VPE_CHAN_LUMA1_IN, |
| .vb_index = 0, |
| .vb_part = VPE_LUMA, |
| }, |
| [VPE_PORT_CHROMA1_IN] = { |
| .channel = VPE_CHAN_CHROMA1_IN, |
| .vb_index = 0, |
| .vb_part = VPE_CHROMA, |
| }, |
| [VPE_PORT_LUMA2_IN] = { |
| .channel = VPE_CHAN_LUMA2_IN, |
| .vb_index = 1, |
| .vb_part = VPE_LUMA, |
| }, |
| [VPE_PORT_CHROMA2_IN] = { |
| .channel = VPE_CHAN_CHROMA2_IN, |
| .vb_index = 1, |
| .vb_part = VPE_CHROMA, |
| }, |
| [VPE_PORT_LUMA3_IN] = { |
| .channel = VPE_CHAN_LUMA3_IN, |
| .vb_index = 2, |
| .vb_part = VPE_LUMA, |
| }, |
| [VPE_PORT_CHROMA3_IN] = { |
| .channel = VPE_CHAN_CHROMA3_IN, |
| .vb_index = 2, |
| .vb_part = VPE_CHROMA, |
| }, |
| [VPE_PORT_MV_IN] = { |
| .channel = VPE_CHAN_MV_IN, |
| }, |
| [VPE_PORT_MV_OUT] = { |
| .channel = VPE_CHAN_MV_OUT, |
| }, |
| [VPE_PORT_LUMA_OUT] = { |
| .channel = VPE_CHAN_LUMA_OUT, |
| .vb_part = VPE_LUMA, |
| }, |
| [VPE_PORT_CHROMA_OUT] = { |
| .channel = VPE_CHAN_CHROMA_OUT, |
| .vb_part = VPE_CHROMA, |
| }, |
| [VPE_PORT_RGB_OUT] = { |
| .channel = VPE_CHAN_RGB_OUT, |
| .vb_part = VPE_LUMA, |
| }, |
| }; |
| |
| |
| /* driver info for each of the supported video formats */ |
| struct vpe_fmt { |
| char *name; /* human-readable name */ |
| u32 fourcc; /* standard format identifier */ |
| u8 types; /* CAPTURE and/or OUTPUT */ |
| u8 coplanar; /* set for unpacked Luma and Chroma */ |
| /* vpdma format info for each plane */ |
| struct vpdma_data_format const *vpdma_fmt[VPE_MAX_PLANES]; |
| }; |
| |
| static struct vpe_fmt vpe_formats[] = { |
| { |
| .name = "YUV 422 co-planar", |
| .fourcc = V4L2_PIX_FMT_NV16, |
| .types = VPE_FMT_TYPE_CAPTURE | VPE_FMT_TYPE_OUTPUT, |
| .coplanar = 1, |
| .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_Y444], |
| &vpdma_yuv_fmts[VPDMA_DATA_FMT_C444], |
| }, |
| }, |
| { |
| .name = "YUV 420 co-planar", |
| .fourcc = V4L2_PIX_FMT_NV12, |
| .types = VPE_FMT_TYPE_CAPTURE | VPE_FMT_TYPE_OUTPUT, |
| .coplanar = 1, |
| .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_Y420], |
| &vpdma_yuv_fmts[VPDMA_DATA_FMT_C420], |
| }, |
| }, |
| { |
| .name = "YUYV 422 packed", |
| .fourcc = V4L2_PIX_FMT_YUYV, |
| .types = VPE_FMT_TYPE_CAPTURE | VPE_FMT_TYPE_OUTPUT, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_YC422], |
| }, |
| }, |
| { |
| .name = "UYVY 422 packed", |
| .fourcc = V4L2_PIX_FMT_UYVY, |
| .types = VPE_FMT_TYPE_CAPTURE | VPE_FMT_TYPE_OUTPUT, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_yuv_fmts[VPDMA_DATA_FMT_CY422], |
| }, |
| }, |
| { |
| .name = "RGB888 packed", |
| .fourcc = V4L2_PIX_FMT_RGB24, |
| .types = VPE_FMT_TYPE_CAPTURE, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_RGB24], |
| }, |
| }, |
| { |
| .name = "ARGB32", |
| .fourcc = V4L2_PIX_FMT_RGB32, |
| .types = VPE_FMT_TYPE_CAPTURE, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ARGB32], |
| }, |
| }, |
| { |
| .name = "BGR888 packed", |
| .fourcc = V4L2_PIX_FMT_BGR24, |
| .types = VPE_FMT_TYPE_CAPTURE, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_BGR24], |
| }, |
| }, |
| { |
| .name = "ABGR32", |
| .fourcc = V4L2_PIX_FMT_BGR32, |
| .types = VPE_FMT_TYPE_CAPTURE, |
| .coplanar = 0, |
| .vpdma_fmt = { &vpdma_rgb_fmts[VPDMA_DATA_FMT_ABGR32], |
| }, |
| }, |
| }; |
| |
| /* |
| * per-queue, driver-specific private data. |
| * there is one source queue and one destination queue for each m2m context. |
| */ |
| struct vpe_q_data { |
| unsigned int width; /* frame width */ |
| unsigned int height; /* frame height */ |
| unsigned int bytesperline[VPE_MAX_PLANES]; /* bytes per line in memory */ |
| enum v4l2_colorspace colorspace; |
| enum v4l2_field field; /* supported field value */ |
| unsigned int flags; |
| unsigned int sizeimage[VPE_MAX_PLANES]; /* image size in memory */ |
| struct v4l2_rect c_rect; /* crop/compose rectangle */ |
| struct vpe_fmt *fmt; /* format info */ |
| }; |
| |
| /* vpe_q_data flag bits */ |
| #define Q_DATA_FRAME_1D (1 << 0) |
| #define Q_DATA_MODE_TILED (1 << 1) |
| #define Q_DATA_INTERLACED (1 << 2) |
| |
| enum { |
| Q_DATA_SRC = 0, |
| Q_DATA_DST = 1, |
| }; |
| |
| /* find our format description corresponding to the passed v4l2_format */ |
| static struct vpe_fmt *find_format(struct v4l2_format *f) |
| { |
| struct vpe_fmt *fmt; |
| unsigned int k; |
| |
| for (k = 0; k < ARRAY_SIZE(vpe_formats); k++) { |
| fmt = &vpe_formats[k]; |
| if (fmt->fourcc == f->fmt.pix.pixelformat) |
| return fmt; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * there is one vpe_dev structure in the driver, it is shared by |
| * all instances. |
| */ |
| struct vpe_dev { |
| struct v4l2_device v4l2_dev; |
| struct video_device vfd; |
| struct v4l2_m2m_dev *m2m_dev; |
| |
| atomic_t num_instances; /* count of driver instances */ |
| dma_addr_t loaded_mmrs; /* shadow mmrs in device */ |
| struct mutex dev_mutex; |
| spinlock_t lock; |
| |
| int irq; |
| void __iomem *base; |
| struct resource *res; |
| |
| struct vb2_alloc_ctx *alloc_ctx; |
| struct vpdma_data *vpdma; /* vpdma data handle */ |
| struct sc_data *sc; /* scaler data handle */ |
| struct csc_data *csc; /* csc data handle */ |
| }; |
| |
| /* |
| * There is one vpe_ctx structure for each m2m context. |
| */ |
| struct vpe_ctx { |
| struct v4l2_fh fh; |
| struct vpe_dev *dev; |
| struct v4l2_m2m_ctx *m2m_ctx; |
| struct v4l2_ctrl_handler hdl; |
| |
| unsigned int field; /* current field */ |
| unsigned int sequence; /* current frame/field seq */ |
| unsigned int aborting; /* abort after next irq */ |
| |
| unsigned int bufs_per_job; /* input buffers per batch */ |
| unsigned int bufs_completed; /* bufs done in this batch */ |
| |
| struct vpe_q_data q_data[2]; /* src & dst queue data */ |
| struct vb2_buffer *src_vbs[VPE_MAX_SRC_BUFS]; |
| struct vb2_buffer *dst_vb; |
| |
| dma_addr_t mv_buf_dma[2]; /* dma addrs of motion vector in/out bufs */ |
| void *mv_buf[2]; /* virtual addrs of motion vector bufs */ |
| size_t mv_buf_size; /* current motion vector buffer size */ |
| struct vpdma_buf mmr_adb; /* shadow reg addr/data block */ |
| struct vpdma_buf sc_coeff_h; /* h coeff buffer */ |
| struct vpdma_buf sc_coeff_v; /* v coeff buffer */ |
| struct vpdma_desc_list desc_list; /* DMA descriptor list */ |
| |
| bool deinterlacing; /* using de-interlacer */ |
| bool load_mmrs; /* have new shadow reg values */ |
| |
| unsigned int src_mv_buf_selector; |
| }; |
| |
| |
| /* |
| * M2M devices get 2 queues. |
| * Return the queue given the type. |
| */ |
| static struct vpe_q_data *get_q_data(struct vpe_ctx *ctx, |
| enum v4l2_buf_type type) |
| { |
| switch (type) { |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT: |
| return &ctx->q_data[Q_DATA_SRC]; |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE: |
| return &ctx->q_data[Q_DATA_DST]; |
| default: |
| BUG(); |
| } |
| return NULL; |
| } |
| |
| static u32 read_reg(struct vpe_dev *dev, int offset) |
| { |
| return ioread32(dev->base + offset); |
| } |
| |
| static void write_reg(struct vpe_dev *dev, int offset, u32 value) |
| { |
| iowrite32(value, dev->base + offset); |
| } |
| |
| /* register field read/write helpers */ |
| static int get_field(u32 value, u32 mask, int shift) |
| { |
| return (value & (mask << shift)) >> shift; |
| } |
| |
| static int read_field_reg(struct vpe_dev *dev, int offset, u32 mask, int shift) |
| { |
| return get_field(read_reg(dev, offset), mask, shift); |
| } |
| |
| static void write_field(u32 *valp, u32 field, u32 mask, int shift) |
| { |
| u32 val = *valp; |
| |
| val &= ~(mask << shift); |
| val |= (field & mask) << shift; |
| *valp = val; |
| } |
| |
| static void write_field_reg(struct vpe_dev *dev, int offset, u32 field, |
| u32 mask, int shift) |
| { |
| u32 val = read_reg(dev, offset); |
| |
| write_field(&val, field, mask, shift); |
| |
| write_reg(dev, offset, val); |
| } |
| |
| /* |
| * DMA address/data block for the shadow registers |
| */ |
| struct vpe_mmr_adb { |
| struct vpdma_adb_hdr out_fmt_hdr; |
| u32 out_fmt_reg[1]; |
| u32 out_fmt_pad[3]; |
| struct vpdma_adb_hdr us1_hdr; |
| u32 us1_regs[8]; |
| struct vpdma_adb_hdr us2_hdr; |
| u32 us2_regs[8]; |
| struct vpdma_adb_hdr us3_hdr; |
| u32 us3_regs[8]; |
| struct vpdma_adb_hdr dei_hdr; |
| u32 dei_regs[8]; |
| struct vpdma_adb_hdr sc_hdr0; |
| u32 sc_regs0[7]; |
| u32 sc_pad0[1]; |
| struct vpdma_adb_hdr sc_hdr8; |
| u32 sc_regs8[6]; |
| u32 sc_pad8[2]; |
| struct vpdma_adb_hdr sc_hdr17; |
| u32 sc_regs17[9]; |
| u32 sc_pad17[3]; |
| struct vpdma_adb_hdr csc_hdr; |
| u32 csc_regs[6]; |
| u32 csc_pad[2]; |
| }; |
| |
| #define GET_OFFSET_TOP(ctx, obj, reg) \ |
| ((obj)->res->start - ctx->dev->res->start + reg) |
| |
| #define VPE_SET_MMR_ADB_HDR(ctx, hdr, regs, offset_a) \ |
| VPDMA_SET_MMR_ADB_HDR(ctx->mmr_adb, vpe_mmr_adb, hdr, regs, offset_a) |
| /* |
| * Set the headers for all of the address/data block structures. |
| */ |
| static void init_adb_hdrs(struct vpe_ctx *ctx) |
| { |
| VPE_SET_MMR_ADB_HDR(ctx, out_fmt_hdr, out_fmt_reg, VPE_CLK_FORMAT_SELECT); |
| VPE_SET_MMR_ADB_HDR(ctx, us1_hdr, us1_regs, VPE_US1_R0); |
| VPE_SET_MMR_ADB_HDR(ctx, us2_hdr, us2_regs, VPE_US2_R0); |
| VPE_SET_MMR_ADB_HDR(ctx, us3_hdr, us3_regs, VPE_US3_R0); |
| VPE_SET_MMR_ADB_HDR(ctx, dei_hdr, dei_regs, VPE_DEI_FRAME_SIZE); |
| VPE_SET_MMR_ADB_HDR(ctx, sc_hdr0, sc_regs0, |
| GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC0)); |
| VPE_SET_MMR_ADB_HDR(ctx, sc_hdr8, sc_regs8, |
| GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC8)); |
| VPE_SET_MMR_ADB_HDR(ctx, sc_hdr17, sc_regs17, |
| GET_OFFSET_TOP(ctx, ctx->dev->sc, CFG_SC17)); |
| VPE_SET_MMR_ADB_HDR(ctx, csc_hdr, csc_regs, |
| GET_OFFSET_TOP(ctx, ctx->dev->csc, CSC_CSC00)); |
| }; |
| |
| /* |
| * Allocate or re-allocate the motion vector DMA buffers |
| * There are two buffers, one for input and one for output. |
| * However, the roles are reversed after each field is processed. |
| * In other words, after each field is processed, the previous |
| * output (dst) MV buffer becomes the new input (src) MV buffer. |
| */ |
| static int realloc_mv_buffers(struct vpe_ctx *ctx, size_t size) |
| { |
| struct device *dev = ctx->dev->v4l2_dev.dev; |
| |
| if (ctx->mv_buf_size == size) |
| return 0; |
| |
| if (ctx->mv_buf[0]) |
| dma_free_coherent(dev, ctx->mv_buf_size, ctx->mv_buf[0], |
| ctx->mv_buf_dma[0]); |
| |
| if (ctx->mv_buf[1]) |
| dma_free_coherent(dev, ctx->mv_buf_size, ctx->mv_buf[1], |
| ctx->mv_buf_dma[1]); |
| |
| if (size == 0) |
| return 0; |
| |
| ctx->mv_buf[0] = dma_alloc_coherent(dev, size, &ctx->mv_buf_dma[0], |
| GFP_KERNEL); |
| if (!ctx->mv_buf[0]) { |
| vpe_err(ctx->dev, "failed to allocate motion vector buffer\n"); |
| return -ENOMEM; |
| } |
| |
| ctx->mv_buf[1] = dma_alloc_coherent(dev, size, &ctx->mv_buf_dma[1], |
| GFP_KERNEL); |
| if (!ctx->mv_buf[1]) { |
| vpe_err(ctx->dev, "failed to allocate motion vector buffer\n"); |
| dma_free_coherent(dev, size, ctx->mv_buf[0], |
| ctx->mv_buf_dma[0]); |
| |
| return -ENOMEM; |
| } |
| |
| ctx->mv_buf_size = size; |
| ctx->src_mv_buf_selector = 0; |
| |
| return 0; |
| } |
| |
| static void free_mv_buffers(struct vpe_ctx *ctx) |
| { |
| realloc_mv_buffers(ctx, 0); |
| } |
| |
| /* |
| * While de-interlacing, we keep the two most recent input buffers |
| * around. This function frees those two buffers when we have |
| * finished processing the current stream. |
| */ |
| static void free_vbs(struct vpe_ctx *ctx) |
| { |
| struct vpe_dev *dev = ctx->dev; |
| unsigned long flags; |
| |
| if (ctx->src_vbs[2] == NULL) |
| return; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (ctx->src_vbs[2]) { |
| v4l2_m2m_buf_done(ctx->src_vbs[2], VB2_BUF_STATE_DONE); |
| v4l2_m2m_buf_done(ctx->src_vbs[1], VB2_BUF_STATE_DONE); |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| } |
| |
| /* |
| * Enable or disable the VPE clocks |
| */ |
| static void vpe_set_clock_enable(struct vpe_dev *dev, bool on) |
| { |
| u32 val = 0; |
| |
| if (on) |
| val = VPE_DATA_PATH_CLK_ENABLE | VPE_VPEDMA_CLK_ENABLE; |
| write_reg(dev, VPE_CLK_ENABLE, val); |
| } |
| |
| static void vpe_top_reset(struct vpe_dev *dev) |
| { |
| |
| write_field_reg(dev, VPE_CLK_RESET, 1, VPE_DATA_PATH_CLK_RESET_MASK, |
| VPE_DATA_PATH_CLK_RESET_SHIFT); |
| |
| usleep_range(100, 150); |
| |
| write_field_reg(dev, VPE_CLK_RESET, 0, VPE_DATA_PATH_CLK_RESET_MASK, |
| VPE_DATA_PATH_CLK_RESET_SHIFT); |
| } |
| |
| static void vpe_top_vpdma_reset(struct vpe_dev *dev) |
| { |
| write_field_reg(dev, VPE_CLK_RESET, 1, VPE_VPDMA_CLK_RESET_MASK, |
| VPE_VPDMA_CLK_RESET_SHIFT); |
| |
| usleep_range(100, 150); |
| |
| write_field_reg(dev, VPE_CLK_RESET, 0, VPE_VPDMA_CLK_RESET_MASK, |
| VPE_VPDMA_CLK_RESET_SHIFT); |
| } |
| |
| /* |
| * Load the correct of upsampler coefficients into the shadow MMRs |
| */ |
| static void set_us_coefficients(struct vpe_ctx *ctx) |
| { |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| struct vpe_q_data *s_q_data = &ctx->q_data[Q_DATA_SRC]; |
| u32 *us1_reg = &mmr_adb->us1_regs[0]; |
| u32 *us2_reg = &mmr_adb->us2_regs[0]; |
| u32 *us3_reg = &mmr_adb->us3_regs[0]; |
| const unsigned short *cp, *end_cp; |
| |
| cp = &us_coeffs[0].anchor_fid0_c0; |
| |
| if (s_q_data->flags & Q_DATA_INTERLACED) /* interlaced */ |
| cp += sizeof(us_coeffs[0]) / sizeof(*cp); |
| |
| end_cp = cp + sizeof(us_coeffs[0]) / sizeof(*cp); |
| |
| while (cp < end_cp) { |
| write_field(us1_reg, *cp++, VPE_US_C0_MASK, VPE_US_C0_SHIFT); |
| write_field(us1_reg, *cp++, VPE_US_C1_MASK, VPE_US_C1_SHIFT); |
| *us2_reg++ = *us1_reg; |
| *us3_reg++ = *us1_reg++; |
| } |
| ctx->load_mmrs = true; |
| } |
| |
| /* |
| * Set the upsampler config mode and the VPDMA line mode in the shadow MMRs. |
| */ |
| static void set_cfg_and_line_modes(struct vpe_ctx *ctx) |
| { |
| struct vpe_fmt *fmt = ctx->q_data[Q_DATA_SRC].fmt; |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| u32 *us1_reg0 = &mmr_adb->us1_regs[0]; |
| u32 *us2_reg0 = &mmr_adb->us2_regs[0]; |
| u32 *us3_reg0 = &mmr_adb->us3_regs[0]; |
| int line_mode = 1; |
| int cfg_mode = 1; |
| |
| /* |
| * Cfg Mode 0: YUV420 source, enable upsampler, DEI is de-interlacing. |
| * Cfg Mode 1: YUV422 source, disable upsampler, DEI is de-interlacing. |
| */ |
| |
| if (fmt->fourcc == V4L2_PIX_FMT_NV12) { |
| cfg_mode = 0; |
| line_mode = 0; /* double lines to line buffer */ |
| } |
| |
| write_field(us1_reg0, cfg_mode, VPE_US_MODE_MASK, VPE_US_MODE_SHIFT); |
| write_field(us2_reg0, cfg_mode, VPE_US_MODE_MASK, VPE_US_MODE_SHIFT); |
| write_field(us3_reg0, cfg_mode, VPE_US_MODE_MASK, VPE_US_MODE_SHIFT); |
| |
| /* regs for now */ |
| vpdma_set_line_mode(ctx->dev->vpdma, line_mode, VPE_CHAN_CHROMA1_IN); |
| vpdma_set_line_mode(ctx->dev->vpdma, line_mode, VPE_CHAN_CHROMA2_IN); |
| vpdma_set_line_mode(ctx->dev->vpdma, line_mode, VPE_CHAN_CHROMA3_IN); |
| |
| /* frame start for input luma */ |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_LUMA1_IN); |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_LUMA2_IN); |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_LUMA3_IN); |
| |
| /* frame start for input chroma */ |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_CHROMA1_IN); |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_CHROMA2_IN); |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_CHROMA3_IN); |
| |
| /* frame start for MV in client */ |
| vpdma_set_frame_start_event(ctx->dev->vpdma, VPDMA_FSEVENT_CHANNEL_ACTIVE, |
| VPE_CHAN_MV_IN); |
| |
| ctx->load_mmrs = true; |
| } |
| |
| /* |
| * Set the shadow registers that are modified when the source |
| * format changes. |
| */ |
| static void set_src_registers(struct vpe_ctx *ctx) |
| { |
| set_us_coefficients(ctx); |
| } |
| |
| /* |
| * Set the shadow registers that are modified when the destination |
| * format changes. |
| */ |
| static void set_dst_registers(struct vpe_ctx *ctx) |
| { |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| enum v4l2_colorspace clrspc = ctx->q_data[Q_DATA_DST].colorspace; |
| struct vpe_fmt *fmt = ctx->q_data[Q_DATA_DST].fmt; |
| u32 val = 0; |
| |
| if (clrspc == V4L2_COLORSPACE_SRGB) |
| val |= VPE_RGB_OUT_SELECT; |
| else if (fmt->fourcc == V4L2_PIX_FMT_NV16) |
| val |= VPE_COLOR_SEPARATE_422; |
| |
| /* |
| * the source of CHR_DS and CSC is always the scaler, irrespective of |
| * whether it's used or not |
| */ |
| val |= VPE_DS_SRC_DEI_SCALER | VPE_CSC_SRC_DEI_SCALER; |
| |
| if (fmt->fourcc != V4L2_PIX_FMT_NV12) |
| val |= VPE_DS_BYPASS; |
| |
| mmr_adb->out_fmt_reg[0] = val; |
| |
| ctx->load_mmrs = true; |
| } |
| |
| /* |
| * Set the de-interlacer shadow register values |
| */ |
| static void set_dei_regs(struct vpe_ctx *ctx) |
| { |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| struct vpe_q_data *s_q_data = &ctx->q_data[Q_DATA_SRC]; |
| unsigned int src_h = s_q_data->c_rect.height; |
| unsigned int src_w = s_q_data->c_rect.width; |
| u32 *dei_mmr0 = &mmr_adb->dei_regs[0]; |
| bool deinterlace = true; |
| u32 val = 0; |
| |
| /* |
| * according to TRM, we should set DEI in progressive bypass mode when |
| * the input content is progressive, however, DEI is bypassed correctly |
| * for both progressive and interlace content in interlace bypass mode. |
| * It has been recommended not to use progressive bypass mode. |
| */ |
| if ((!ctx->deinterlacing && (s_q_data->flags & Q_DATA_INTERLACED)) || |
| !(s_q_data->flags & Q_DATA_INTERLACED)) { |
| deinterlace = false; |
| val = VPE_DEI_INTERLACE_BYPASS; |
| } |
| |
| src_h = deinterlace ? src_h * 2 : src_h; |
| |
| val |= (src_h << VPE_DEI_HEIGHT_SHIFT) | |
| (src_w << VPE_DEI_WIDTH_SHIFT) | |
| VPE_DEI_FIELD_FLUSH; |
| |
| *dei_mmr0 = val; |
| |
| ctx->load_mmrs = true; |
| } |
| |
| static void set_dei_shadow_registers(struct vpe_ctx *ctx) |
| { |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| u32 *dei_mmr = &mmr_adb->dei_regs[0]; |
| const struct vpe_dei_regs *cur = &dei_regs; |
| |
| dei_mmr[2] = cur->mdt_spacial_freq_thr_reg; |
| dei_mmr[3] = cur->edi_config_reg; |
| dei_mmr[4] = cur->edi_lut_reg0; |
| dei_mmr[5] = cur->edi_lut_reg1; |
| dei_mmr[6] = cur->edi_lut_reg2; |
| dei_mmr[7] = cur->edi_lut_reg3; |
| |
| ctx->load_mmrs = true; |
| } |
| |
| /* |
| * Set the shadow registers whose values are modified when either the |
| * source or destination format is changed. |
| */ |
| static int set_srcdst_params(struct vpe_ctx *ctx) |
| { |
| struct vpe_q_data *s_q_data = &ctx->q_data[Q_DATA_SRC]; |
| struct vpe_q_data *d_q_data = &ctx->q_data[Q_DATA_DST]; |
| struct vpe_mmr_adb *mmr_adb = ctx->mmr_adb.addr; |
| unsigned int src_w = s_q_data->c_rect.width; |
| unsigned int src_h = s_q_data->c_rect.height; |
| unsigned int dst_w = d_q_data->c_rect.width; |
| unsigned int dst_h = d_q_data->c_rect.height; |
| size_t mv_buf_size; |
| int ret; |
| |
| ctx->sequence = 0; |
| ctx->field = V4L2_FIELD_TOP; |
| |
| if ((s_q_data->flags & Q_DATA_INTERLACED) && |
| !(d_q_data->flags & Q_DATA_INTERLACED)) { |
| int bytes_per_line; |
| const struct vpdma_data_format *mv = |
| &vpdma_misc_fmts[VPDMA_DATA_FMT_MV]; |
| |
| /* |
| * we make sure that the source image has a 16 byte aligned |
| * stride, we need to do the same for the motion vector buffer |
| * by aligning it's stride to the next 16 byte boundry. this |
| * extra space will not be used by the de-interlacer, but will |
| * ensure that vpdma operates correctly |
| */ |
| bytes_per_line = ALIGN((s_q_data->width * mv->depth) >> 3, |
| VPDMA_STRIDE_ALIGN); |
| mv_buf_size = bytes_per_line * s_q_data->height; |
| |
| ctx->deinterlacing = 1; |
| src_h <<= 1; |
| } else { |
| ctx->deinterlacing = 0; |
| mv_buf_size = 0; |
| } |
| |
| free_vbs(ctx); |
| |
| ret = realloc_mv_buffers(ctx, mv_buf_size); |
| if (ret) |
| return ret; |
| |
| set_cfg_and_line_modes(ctx); |
| set_dei_regs(ctx); |
| |
| csc_set_coeff(ctx->dev->csc, &mmr_adb->csc_regs[0], |
| s_q_data->colorspace, d_q_data->colorspace); |
| |
| sc_set_hs_coeffs(ctx->dev->sc, ctx->sc_coeff_h.addr, src_w, dst_w); |
| sc_set_vs_coeffs(ctx->dev->sc, ctx->sc_coeff_v.addr, src_h, dst_h); |
| |
| sc_config_scaler(ctx->dev->sc, &mmr_adb->sc_regs0[0], |
| &mmr_adb->sc_regs8[0], &mmr_adb->sc_regs17[0], |
| src_w, src_h, dst_w, dst_h); |
| |
| return 0; |
| } |
| |
| /* |
| * Return the vpe_ctx structure for a given struct file |
| */ |
| static struct vpe_ctx *file2ctx(struct file *file) |
| { |
| return container_of(file->private_data, struct vpe_ctx, fh); |
| } |
| |
| /* |
| * mem2mem callbacks |
| */ |
| |
| /** |
| * job_ready() - check whether an instance is ready to be scheduled to run |
| */ |
| static int job_ready(void *priv) |
| { |
| struct vpe_ctx *ctx = priv; |
| int needed = ctx->bufs_per_job; |
| |
| if (ctx->deinterlacing && ctx->src_vbs[2] == NULL) |
| needed += 2; /* need additional two most recent fields */ |
| |
| if (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) < needed) |
| return 0; |
| |
| if (v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) < needed) |
| return 0; |
| |
| return 1; |
| } |
| |
| static void job_abort(void *priv) |
| { |
| struct vpe_ctx *ctx = priv; |
| |
| /* Will cancel the transaction in the next interrupt handler */ |
| ctx->aborting = 1; |
| } |
| |
| /* |
| * Lock access to the device |
| */ |
| static void vpe_lock(void *priv) |
| { |
| struct vpe_ctx *ctx = priv; |
| struct vpe_dev *dev = ctx->dev; |
| mutex_lock(&dev->dev_mutex); |
| } |
| |
| static void vpe_unlock(void *priv) |
| { |
| struct vpe_ctx *ctx = priv; |
| struct vpe_dev *dev = ctx->dev; |
| mutex_unlock(&dev->dev_mutex); |
| } |
| |
| static void vpe_dump_regs(struct vpe_dev *dev) |
| { |
| #define DUMPREG(r) vpe_dbg(dev, "%-35s %08x\n", #r, read_reg(dev, VPE_##r)) |
| |
| vpe_dbg(dev, "VPE Registers:\n"); |
| |
| DUMPREG(PID); |
| DUMPREG(SYSCONFIG); |
| DUMPREG(INT0_STATUS0_RAW); |
| DUMPREG(INT0_STATUS0); |
| DUMPREG(INT0_ENABLE0); |
| DUMPREG(INT0_STATUS1_RAW); |
| DUMPREG(INT0_STATUS1); |
| DUMPREG(INT0_ENABLE1); |
| DUMPREG(CLK_ENABLE); |
| DUMPREG(CLK_RESET); |
| DUMPREG(CLK_FORMAT_SELECT); |
| DUMPREG(CLK_RANGE_MAP); |
| DUMPREG(US1_R0); |
| DUMPREG(US1_R1); |
| DUMPREG(US1_R2); |
| DUMPREG(US1_R3); |
| DUMPREG(US1_R4); |
| DUMPREG(US1_R5); |
| DUMPREG(US1_R6); |
| DUMPREG(US1_R7); |
| DUMPREG(US2_R0); |
| DUMPREG(US2_R1); |
| DUMPREG(US2_R2); |
| DUMPREG(US2_R3); |
| DUMPREG(US2_R4); |
| DUMPREG(US2_R5); |
| DUMPREG(US2_R6); |
| DUMPREG(US2_R7); |
| DUMPREG(US3_R0); |
| DUMPREG(US3_R1); |
| DUMPREG(US3_R2); |
| DUMPREG(US3_R3); |
| DUMPREG(US3_R4); |
| DUMPREG(US3_R5); |
| DUMPREG(US3_R6); |
| DUMPREG(US3_R7); |
| DUMPREG(DEI_FRAME_SIZE); |
| DUMPREG(MDT_BYPASS); |
| DUMPREG(MDT_SF_THRESHOLD); |
| DUMPREG(EDI_CONFIG); |
| DUMPREG(DEI_EDI_LUT_R0); |
| DUMPREG(DEI_EDI_LUT_R1); |
| DUMPREG(DEI_EDI_LUT_R2); |
| DUMPREG(DEI_EDI_LUT_R3); |
| DUMPREG(DEI_FMD_WINDOW_R0); |
| DUMPREG(DEI_FMD_WINDOW_R1); |
| DUMPREG(DEI_FMD_CONTROL_R0); |
| DUMPREG(DEI_FMD_CONTROL_R1); |
| DUMPREG(DEI_FMD_STATUS_R0); |
| DUMPREG(DEI_FMD_STATUS_R1); |
| DUMPREG(DEI_FMD_STATUS_R2); |
| #undef DUMPREG |
| |
| sc_dump_regs(dev->sc); |
| csc_dump_regs(dev->csc); |
| } |
| |
| static void add_out_dtd(struct vpe_ctx *ctx, int port) |
| { |
| struct vpe_q_data *q_data = &ctx->q_data[Q_DATA_DST]; |
| const struct vpe_port_data *p_data = &port_data[port]; |
| struct vb2_buffer *vb = ctx->dst_vb; |
| struct vpe_fmt *fmt = q_data->fmt; |
| const struct vpdma_data_format *vpdma_fmt; |
| int mv_buf_selector = !ctx->src_mv_buf_selector; |
| dma_addr_t dma_addr; |
| u32 flags = 0; |
| |
| if (port == VPE_PORT_MV_OUT) { |
| vpdma_fmt = &vpdma_misc_fmts[VPDMA_DATA_FMT_MV]; |
| dma_addr = ctx->mv_buf_dma[mv_buf_selector]; |
| } else { |
| /* to incorporate interleaved formats */ |
| int plane = fmt->coplanar ? p_data->vb_part : 0; |
| |
| vpdma_fmt = fmt->vpdma_fmt[plane]; |
| dma_addr = vb2_dma_contig_plane_dma_addr(vb, plane); |
| if (!dma_addr) { |
| vpe_err(ctx->dev, |
| "acquiring output buffer(%d) dma_addr failed\n", |
| port); |
| return; |
| } |
| } |
| |
| if (q_data->flags & Q_DATA_FRAME_1D) |
| flags |= VPDMA_DATA_FRAME_1D; |
| if (q_data->flags & Q_DATA_MODE_TILED) |
| flags |= VPDMA_DATA_MODE_TILED; |
| |
| vpdma_add_out_dtd(&ctx->desc_list, q_data->width, &q_data->c_rect, |
| vpdma_fmt, dma_addr, p_data->channel, flags); |
| } |
| |
| static void add_in_dtd(struct vpe_ctx *ctx, int port) |
| { |
| struct vpe_q_data *q_data = &ctx->q_data[Q_DATA_SRC]; |
| const struct vpe_port_data *p_data = &port_data[port]; |
| struct vb2_buffer *vb = ctx->src_vbs[p_data->vb_index]; |
| struct vpe_fmt *fmt = q_data->fmt; |
| const struct vpdma_data_format *vpdma_fmt; |
| int mv_buf_selector = ctx->src_mv_buf_selector; |
| int field = vb->v4l2_buf.field == V4L2_FIELD_BOTTOM; |
| int frame_width, frame_height; |
| dma_addr_t dma_addr; |
| u32 flags = 0; |
| |
| if (port == VPE_PORT_MV_IN) { |
| vpdma_fmt = &vpdma_misc_fmts[VPDMA_DATA_FMT_MV]; |
| dma_addr = ctx->mv_buf_dma[mv_buf_selector]; |
| } else { |
| /* to incorporate interleaved formats */ |
| int plane = fmt->coplanar ? p_data->vb_part : 0; |
| |
| vpdma_fmt = fmt->vpdma_fmt[plane]; |
| |
| dma_addr = vb2_dma_contig_plane_dma_addr(vb, plane); |
| if (!dma_addr) { |
| vpe_err(ctx->dev, |
| "acquiring input buffer(%d) dma_addr failed\n", |
| port); |
| return; |
| } |
| } |
| |
| if (q_data->flags & Q_DATA_FRAME_1D) |
| flags |= VPDMA_DATA_FRAME_1D; |
| if (q_data->flags & Q_DATA_MODE_TILED) |
| flags |= VPDMA_DATA_MODE_TILED; |
| |
| frame_width = q_data->c_rect.width; |
| frame_height = q_data->c_rect.height; |
| |
| if (p_data->vb_part && fmt->fourcc == V4L2_PIX_FMT_NV12) |
| frame_height /= 2; |
| |
| vpdma_add_in_dtd(&ctx->desc_list, q_data->width, &q_data->c_rect, |
| vpdma_fmt, dma_addr, p_data->channel, field, flags, frame_width, |
| frame_height, 0, 0); |
| } |
| |
| /* |
| * Enable the expected IRQ sources |
| */ |
| static void enable_irqs(struct vpe_ctx *ctx) |
| { |
| write_reg(ctx->dev, VPE_INT0_ENABLE0_SET, VPE_INT0_LIST0_COMPLETE); |
| write_reg(ctx->dev, VPE_INT0_ENABLE1_SET, VPE_DEI_ERROR_INT | |
| VPE_DS1_UV_ERROR_INT); |
| |
| vpdma_enable_list_complete_irq(ctx->dev->vpdma, 0, true); |
| } |
| |
| static void disable_irqs(struct vpe_ctx *ctx) |
| { |
| write_reg(ctx->dev, VPE_INT0_ENABLE0_CLR, 0xffffffff); |
| write_reg(ctx->dev, VPE_INT0_ENABLE1_CLR, 0xffffffff); |
| |
| vpdma_enable_list_complete_irq(ctx->dev->vpdma, 0, false); |
| } |
| |
| /* device_run() - prepares and starts the device |
| * |
| * This function is only called when both the source and destination |
| * buffers are in place. |
| */ |
| static void device_run(void *priv) |
| { |
| struct vpe_ctx *ctx = priv; |
| struct sc_data *sc = ctx->dev->sc; |
| struct vpe_q_data *d_q_data = &ctx->q_data[Q_DATA_DST]; |
| |
| if (ctx->deinterlacing && ctx->src_vbs[2] == NULL) { |
| ctx->src_vbs[2] = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); |
| WARN_ON(ctx->src_vbs[2] == NULL); |
| ctx->src_vbs[1] = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); |
| WARN_ON(ctx->src_vbs[1] == NULL); |
| } |
| |
| ctx->src_vbs[0] = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); |
| WARN_ON(ctx->src_vbs[0] == NULL); |
| ctx->dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); |
| WARN_ON(ctx->dst_vb == NULL); |
| |
| /* config descriptors */ |
| if (ctx->dev->loaded_mmrs != ctx->mmr_adb.dma_addr || ctx->load_mmrs) { |
| vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->mmr_adb); |
| vpdma_add_cfd_adb(&ctx->desc_list, CFD_MMR_CLIENT, &ctx->mmr_adb); |
| ctx->dev->loaded_mmrs = ctx->mmr_adb.dma_addr; |
| ctx->load_mmrs = false; |
| } |
| |
| if (sc->loaded_coeff_h != ctx->sc_coeff_h.dma_addr || |
| sc->load_coeff_h) { |
| vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->sc_coeff_h); |
| vpdma_add_cfd_block(&ctx->desc_list, CFD_SC_CLIENT, |
| &ctx->sc_coeff_h, 0); |
| |
| sc->loaded_coeff_h = ctx->sc_coeff_h.dma_addr; |
| sc->load_coeff_h = false; |
| } |
| |
| if (sc->loaded_coeff_v != ctx->sc_coeff_v.dma_addr || |
| sc->load_coeff_v) { |
| vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->sc_coeff_v); |
| vpdma_add_cfd_block(&ctx->desc_list, CFD_SC_CLIENT, |
| &ctx->sc_coeff_v, SC_COEF_SRAM_SIZE >> 4); |
| |
| sc->loaded_coeff_v = ctx->sc_coeff_v.dma_addr; |
| sc->load_coeff_v = false; |
| } |
| |
| /* output data descriptors */ |
| if (ctx->deinterlacing) |
| add_out_dtd(ctx, VPE_PORT_MV_OUT); |
| |
| if (d_q_data->colorspace == V4L2_COLORSPACE_SRGB) { |
| add_out_dtd(ctx, VPE_PORT_RGB_OUT); |
| } else { |
| add_out_dtd(ctx, VPE_PORT_LUMA_OUT); |
| if (d_q_data->fmt->coplanar) |
| add_out_dtd(ctx, VPE_PORT_CHROMA_OUT); |
| } |
| |
| /* input data descriptors */ |
| if (ctx->deinterlacing) { |
| add_in_dtd(ctx, VPE_PORT_LUMA3_IN); |
| add_in_dtd(ctx, VPE_PORT_CHROMA3_IN); |
| |
| add_in_dtd(ctx, VPE_PORT_LUMA2_IN); |
| add_in_dtd(ctx, VPE_PORT_CHROMA2_IN); |
| } |
| |
| add_in_dtd(ctx, VPE_PORT_LUMA1_IN); |
| add_in_dtd(ctx, VPE_PORT_CHROMA1_IN); |
| |
| if (ctx->deinterlacing) |
| add_in_dtd(ctx, VPE_PORT_MV_IN); |
| |
| /* sync on channel control descriptors for input ports */ |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_LUMA1_IN); |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_CHROMA1_IN); |
| |
| if (ctx->deinterlacing) { |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_LUMA2_IN); |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_CHROMA2_IN); |
| |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_LUMA3_IN); |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_CHROMA3_IN); |
| |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_MV_IN); |
| } |
| |
| /* sync on channel control descriptors for output ports */ |
| if (d_q_data->colorspace == V4L2_COLORSPACE_SRGB) { |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_RGB_OUT); |
| } else { |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_LUMA_OUT); |
| if (d_q_data->fmt->coplanar) |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, |
| VPE_CHAN_CHROMA_OUT); |
| } |
| |
| if (ctx->deinterlacing) |
| vpdma_add_sync_on_channel_ctd(&ctx->desc_list, VPE_CHAN_MV_OUT); |
| |
| enable_irqs(ctx); |
| |
| vpdma_map_desc_buf(ctx->dev->vpdma, &ctx->desc_list.buf); |
| vpdma_submit_descs(ctx->dev->vpdma, &ctx->desc_list); |
| } |
| |
| static void dei_error(struct vpe_ctx *ctx) |
| { |
| dev_warn(ctx->dev->v4l2_dev.dev, |
| "received DEI error interrupt\n"); |
| } |
| |
| static void ds1_uv_error(struct vpe_ctx *ctx) |
| { |
| dev_warn(ctx->dev->v4l2_dev.dev, |
| "received downsampler error interrupt\n"); |
| } |
| |
| static irqreturn_t vpe_irq(int irq_vpe, void *data) |
| { |
| struct vpe_dev *dev = (struct vpe_dev *)data; |
| struct vpe_ctx *ctx; |
| struct vpe_q_data *d_q_data; |
| struct vb2_buffer *s_vb, *d_vb; |
| struct v4l2_buffer *s_buf, *d_buf; |
| unsigned long flags; |
| u32 irqst0, irqst1; |
| |
| irqst0 = read_reg(dev, VPE_INT0_STATUS0); |
| if (irqst0) { |
| write_reg(dev, VPE_INT0_STATUS0_CLR, irqst0); |
| vpe_dbg(dev, "INT0_STATUS0 = 0x%08x\n", irqst0); |
| } |
| |
| irqst1 = read_reg(dev, VPE_INT0_STATUS1); |
| if (irqst1) { |
| write_reg(dev, VPE_INT0_STATUS1_CLR, irqst1); |
| vpe_dbg(dev, "INT0_STATUS1 = 0x%08x\n", irqst1); |
| } |
| |
| ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); |
| if (!ctx) { |
| vpe_err(dev, "instance released before end of transaction\n"); |
| goto handled; |
| } |
| |
| if (irqst1) { |
| if (irqst1 & VPE_DEI_ERROR_INT) { |
| irqst1 &= ~VPE_DEI_ERROR_INT; |
| dei_error(ctx); |
| } |
| if (irqst1 & VPE_DS1_UV_ERROR_INT) { |
| irqst1 &= ~VPE_DS1_UV_ERROR_INT; |
| ds1_uv_error(ctx); |
| } |
| } |
| |
| if (irqst0) { |
| if (irqst0 & VPE_INT0_LIST0_COMPLETE) |
| vpdma_clear_list_stat(ctx->dev->vpdma); |
| |
| irqst0 &= ~(VPE_INT0_LIST0_COMPLETE); |
| } |
| |
| if (irqst0 | irqst1) { |
| dev_warn(dev->v4l2_dev.dev, "Unexpected interrupt: " |
| "INT0_STATUS0 = 0x%08x, INT0_STATUS1 = 0x%08x\n", |
| irqst0, irqst1); |
| } |
| |
| disable_irqs(ctx); |
| |
| vpdma_unmap_desc_buf(dev->vpdma, &ctx->desc_list.buf); |
| vpdma_unmap_desc_buf(dev->vpdma, &ctx->mmr_adb); |
| vpdma_unmap_desc_buf(dev->vpdma, &ctx->sc_coeff_h); |
| vpdma_unmap_desc_buf(dev->vpdma, &ctx->sc_coeff_v); |
| |
| vpdma_reset_desc_list(&ctx->desc_list); |
| |
| /* the previous dst mv buffer becomes the next src mv buffer */ |
| ctx->src_mv_buf_selector = !ctx->src_mv_buf_selector; |
| |
| if (ctx->aborting) |
| goto finished; |
| |
| s_vb = ctx->src_vbs[0]; |
| d_vb = ctx->dst_vb; |
| s_buf = &s_vb->v4l2_buf; |
| d_buf = &d_vb->v4l2_buf; |
| |
| d_buf->flags = s_buf->flags; |
| |
| d_buf->timestamp = s_buf->timestamp; |
| if (s_buf->flags & V4L2_BUF_FLAG_TIMECODE) |
| d_buf->timecode = s_buf->timecode; |
| |
| d_buf->sequence = ctx->sequence; |
| |
| d_q_data = &ctx->q_data[Q_DATA_DST]; |
| if (d_q_data->flags & Q_DATA_INTERLACED) { |
| d_buf->field = ctx->field; |
| if (ctx->field == V4L2_FIELD_BOTTOM) { |
| ctx->sequence++; |
| ctx->field = V4L2_FIELD_TOP; |
| } else { |
| WARN_ON(ctx->field != V4L2_FIELD_TOP); |
| ctx->field = V4L2_FIELD_BOTTOM; |
| } |
| } else { |
| d_buf->field = V4L2_FIELD_NONE; |
| ctx->sequence++; |
| } |
| |
| if (ctx->deinterlacing) |
| s_vb = ctx->src_vbs[2]; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| v4l2_m2m_buf_done(s_vb, VB2_BUF_STATE_DONE); |
| v4l2_m2m_buf_done(d_vb, VB2_BUF_STATE_DONE); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| if (ctx->deinterlacing) { |
| ctx->src_vbs[2] = ctx->src_vbs[1]; |
| ctx->src_vbs[1] = ctx->src_vbs[0]; |
| } |
| |
| ctx->bufs_completed++; |
| if (ctx->bufs_completed < ctx->bufs_per_job) { |
| device_run(ctx); |
| goto handled; |
| } |
| |
| finished: |
| vpe_dbg(ctx->dev, "finishing transaction\n"); |
| ctx->bufs_completed = 0; |
| v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx); |
| handled: |
| return IRQ_HANDLED; |
| } |
| |
| /* |
| * video ioctls |
| */ |
| static int vpe_querycap(struct file *file, void *priv, |
| struct v4l2_capability *cap) |
| { |
| strncpy(cap->driver, VPE_MODULE_NAME, sizeof(cap->driver) - 1); |
| strncpy(cap->card, VPE_MODULE_NAME, sizeof(cap->card) - 1); |
| snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", |
| VPE_MODULE_NAME); |
| cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; |
| cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; |
| return 0; |
| } |
| |
| static int __enum_fmt(struct v4l2_fmtdesc *f, u32 type) |
| { |
| int i, index; |
| struct vpe_fmt *fmt = NULL; |
| |
| index = 0; |
| for (i = 0; i < ARRAY_SIZE(vpe_formats); ++i) { |
| if (vpe_formats[i].types & type) { |
| if (index == f->index) { |
| fmt = &vpe_formats[i]; |
| break; |
| } |
| index++; |
| } |
| } |
| |
| if (!fmt) |
| return -EINVAL; |
| |
| strncpy(f->description, fmt->name, sizeof(f->description) - 1); |
| f->pixelformat = fmt->fourcc; |
| return 0; |
| } |
| |
| static int vpe_enum_fmt(struct file *file, void *priv, |
| struct v4l2_fmtdesc *f) |
| { |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) |
| return __enum_fmt(f, VPE_FMT_TYPE_OUTPUT); |
| |
| return __enum_fmt(f, VPE_FMT_TYPE_CAPTURE); |
| } |
| |
| static int vpe_g_fmt(struct file *file, void *priv, struct v4l2_format *f) |
| { |
| struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vb2_queue *vq; |
| struct vpe_q_data *q_data; |
| int i; |
| |
| vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); |
| if (!vq) |
| return -EINVAL; |
| |
| q_data = get_q_data(ctx, f->type); |
| |
| pix->width = q_data->width; |
| pix->height = q_data->height; |
| pix->pixelformat = q_data->fmt->fourcc; |
| pix->field = q_data->field; |
| |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) { |
| pix->colorspace = q_data->colorspace; |
| } else { |
| struct vpe_q_data *s_q_data; |
| |
| /* get colorspace from the source queue */ |
| s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); |
| |
| pix->colorspace = s_q_data->colorspace; |
| } |
| |
| pix->num_planes = q_data->fmt->coplanar ? 2 : 1; |
| |
| for (i = 0; i < pix->num_planes; i++) { |
| pix->plane_fmt[i].bytesperline = q_data->bytesperline[i]; |
| pix->plane_fmt[i].sizeimage = q_data->sizeimage[i]; |
| } |
| |
| return 0; |
| } |
| |
| static int __vpe_try_fmt(struct vpe_ctx *ctx, struct v4l2_format *f, |
| struct vpe_fmt *fmt, int type) |
| { |
| struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; |
| struct v4l2_plane_pix_format *plane_fmt; |
| unsigned int w_align; |
| int i, depth, depth_bytes; |
| |
| if (!fmt || !(fmt->types & type)) { |
| vpe_err(ctx->dev, "Fourcc format (0x%08x) invalid.\n", |
| pix->pixelformat); |
| return -EINVAL; |
| } |
| |
| if (pix->field != V4L2_FIELD_NONE && pix->field != V4L2_FIELD_ALTERNATE) |
| pix->field = V4L2_FIELD_NONE; |
| |
| depth = fmt->vpdma_fmt[VPE_LUMA]->depth; |
| |
| /* |
| * the line stride should 16 byte aligned for VPDMA to work, based on |
| * the bytes per pixel, figure out how much the width should be aligned |
| * to make sure line stride is 16 byte aligned |
| */ |
| depth_bytes = depth >> 3; |
| |
| if (depth_bytes == 3) |
| /* |
| * if bpp is 3(as in some RGB formats), the pixel width doesn't |
| * really help in ensuring line stride is 16 byte aligned |
| */ |
| w_align = 4; |
| else |
| /* |
| * for the remainder bpp(4, 2 and 1), the pixel width alignment |
| * can ensure a line stride alignment of 16 bytes. For example, |
| * if bpp is 2, then the line stride can be 16 byte aligned if |
| * the width is 8 byte aligned |
| */ |
| w_align = order_base_2(VPDMA_DESC_ALIGN / depth_bytes); |
| |
| v4l_bound_align_image(&pix->width, MIN_W, MAX_W, w_align, |
| &pix->height, MIN_H, MAX_H, H_ALIGN, |
| S_ALIGN); |
| |
| pix->num_planes = fmt->coplanar ? 2 : 1; |
| pix->pixelformat = fmt->fourcc; |
| |
| if (!pix->colorspace) { |
| if (fmt->fourcc == V4L2_PIX_FMT_RGB24 || |
| fmt->fourcc == V4L2_PIX_FMT_BGR24 || |
| fmt->fourcc == V4L2_PIX_FMT_RGB32 || |
| fmt->fourcc == V4L2_PIX_FMT_BGR32) { |
| pix->colorspace = V4L2_COLORSPACE_SRGB; |
| } else { |
| if (pix->height > 1280) /* HD */ |
| pix->colorspace = V4L2_COLORSPACE_REC709; |
| else /* SD */ |
| pix->colorspace = V4L2_COLORSPACE_SMPTE170M; |
| } |
| } |
| |
| memset(pix->reserved, 0, sizeof(pix->reserved)); |
| for (i = 0; i < pix->num_planes; i++) { |
| plane_fmt = &pix->plane_fmt[i]; |
| depth = fmt->vpdma_fmt[i]->depth; |
| |
| if (i == VPE_LUMA) |
| plane_fmt->bytesperline = (pix->width * depth) >> 3; |
| else |
| plane_fmt->bytesperline = pix->width; |
| |
| plane_fmt->sizeimage = |
| (pix->height * pix->width * depth) >> 3; |
| |
| memset(plane_fmt->reserved, 0, sizeof(plane_fmt->reserved)); |
| } |
| |
| return 0; |
| } |
| |
| static int vpe_try_fmt(struct file *file, void *priv, struct v4l2_format *f) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vpe_fmt *fmt = find_format(f); |
| |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) |
| return __vpe_try_fmt(ctx, f, fmt, VPE_FMT_TYPE_OUTPUT); |
| else |
| return __vpe_try_fmt(ctx, f, fmt, VPE_FMT_TYPE_CAPTURE); |
| } |
| |
| static int __vpe_s_fmt(struct vpe_ctx *ctx, struct v4l2_format *f) |
| { |
| struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; |
| struct v4l2_plane_pix_format *plane_fmt; |
| struct vpe_q_data *q_data; |
| struct vb2_queue *vq; |
| int i; |
| |
| vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); |
| if (!vq) |
| return -EINVAL; |
| |
| if (vb2_is_busy(vq)) { |
| vpe_err(ctx->dev, "queue busy\n"); |
| return -EBUSY; |
| } |
| |
| q_data = get_q_data(ctx, f->type); |
| if (!q_data) |
| return -EINVAL; |
| |
| q_data->fmt = find_format(f); |
| q_data->width = pix->width; |
| q_data->height = pix->height; |
| q_data->colorspace = pix->colorspace; |
| q_data->field = pix->field; |
| |
| for (i = 0; i < pix->num_planes; i++) { |
| plane_fmt = &pix->plane_fmt[i]; |
| |
| q_data->bytesperline[i] = plane_fmt->bytesperline; |
| q_data->sizeimage[i] = plane_fmt->sizeimage; |
| } |
| |
| q_data->c_rect.left = 0; |
| q_data->c_rect.top = 0; |
| q_data->c_rect.width = q_data->width; |
| q_data->c_rect.height = q_data->height; |
| |
| if (q_data->field == V4L2_FIELD_ALTERNATE) |
| q_data->flags |= Q_DATA_INTERLACED; |
| else |
| q_data->flags &= ~Q_DATA_INTERLACED; |
| |
| vpe_dbg(ctx->dev, "Setting format for type %d, wxh: %dx%d, fmt: %d bpl_y %d", |
| f->type, q_data->width, q_data->height, q_data->fmt->fourcc, |
| q_data->bytesperline[VPE_LUMA]); |
| if (q_data->fmt->coplanar) |
| vpe_dbg(ctx->dev, " bpl_uv %d\n", |
| q_data->bytesperline[VPE_CHROMA]); |
| |
| return 0; |
| } |
| |
| static int vpe_s_fmt(struct file *file, void *priv, struct v4l2_format *f) |
| { |
| int ret; |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| ret = vpe_try_fmt(file, priv, f); |
| if (ret) |
| return ret; |
| |
| ret = __vpe_s_fmt(ctx, f); |
| if (ret) |
| return ret; |
| |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) |
| set_src_registers(ctx); |
| else |
| set_dst_registers(ctx); |
| |
| return set_srcdst_params(ctx); |
| } |
| |
| static int __vpe_try_selection(struct vpe_ctx *ctx, struct v4l2_selection *s) |
| { |
| struct vpe_q_data *q_data; |
| |
| if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && |
| (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) |
| return -EINVAL; |
| |
| q_data = get_q_data(ctx, s->type); |
| if (!q_data) |
| return -EINVAL; |
| |
| switch (s->target) { |
| case V4L2_SEL_TGT_COMPOSE: |
| /* |
| * COMPOSE target is only valid for capture buffer type, return |
| * error for output buffer type |
| */ |
| if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) |
| return -EINVAL; |
| break; |
| case V4L2_SEL_TGT_CROP: |
| /* |
| * CROP target is only valid for output buffer type, return |
| * error for capture buffer type |
| */ |
| if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| break; |
| /* |
| * bound and default crop/compose targets are invalid targets to |
| * try/set |
| */ |
| default: |
| return -EINVAL; |
| } |
| |
| if (s->r.top < 0 || s->r.left < 0) { |
| vpe_err(ctx->dev, "negative values for top and left\n"); |
| s->r.top = s->r.left = 0; |
| } |
| |
| v4l_bound_align_image(&s->r.width, MIN_W, q_data->width, 1, |
| &s->r.height, MIN_H, q_data->height, H_ALIGN, S_ALIGN); |
| |
| /* adjust left/top if cropping rectangle is out of bounds */ |
| if (s->r.left + s->r.width > q_data->width) |
| s->r.left = q_data->width - s->r.width; |
| if (s->r.top + s->r.height > q_data->height) |
| s->r.top = q_data->height - s->r.height; |
| |
| return 0; |
| } |
| |
| static int vpe_g_selection(struct file *file, void *fh, |
| struct v4l2_selection *s) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vpe_q_data *q_data; |
| bool use_c_rect = false; |
| |
| if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && |
| (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) |
| return -EINVAL; |
| |
| q_data = get_q_data(ctx, s->type); |
| if (!q_data) |
| return -EINVAL; |
| |
| switch (s->target) { |
| case V4L2_SEL_TGT_COMPOSE_DEFAULT: |
| case V4L2_SEL_TGT_COMPOSE_BOUNDS: |
| if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) |
| return -EINVAL; |
| break; |
| case V4L2_SEL_TGT_CROP_BOUNDS: |
| case V4L2_SEL_TGT_CROP_DEFAULT: |
| if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| break; |
| case V4L2_SEL_TGT_COMPOSE: |
| if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) |
| return -EINVAL; |
| use_c_rect = true; |
| break; |
| case V4L2_SEL_TGT_CROP: |
| if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| use_c_rect = true; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| if (use_c_rect) { |
| /* |
| * for CROP/COMPOSE target type, return c_rect params from the |
| * respective buffer type |
| */ |
| s->r = q_data->c_rect; |
| } else { |
| /* |
| * for DEFAULT/BOUNDS target type, return width and height from |
| * S_FMT of the respective buffer type |
| */ |
| s->r.left = 0; |
| s->r.top = 0; |
| s->r.width = q_data->width; |
| s->r.height = q_data->height; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int vpe_s_selection(struct file *file, void *fh, |
| struct v4l2_selection *s) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vpe_q_data *q_data; |
| struct v4l2_selection sel = *s; |
| int ret; |
| |
| ret = __vpe_try_selection(ctx, &sel); |
| if (ret) |
| return ret; |
| |
| q_data = get_q_data(ctx, sel.type); |
| if (!q_data) |
| return -EINVAL; |
| |
| if ((q_data->c_rect.left == sel.r.left) && |
| (q_data->c_rect.top == sel.r.top) && |
| (q_data->c_rect.width == sel.r.width) && |
| (q_data->c_rect.height == sel.r.height)) { |
| vpe_dbg(ctx->dev, |
| "requested crop/compose values are already set\n"); |
| return 0; |
| } |
| |
| q_data->c_rect = sel.r; |
| |
| return set_srcdst_params(ctx); |
| } |
| |
| static int vpe_reqbufs(struct file *file, void *priv, |
| struct v4l2_requestbuffers *reqbufs) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); |
| } |
| |
| static int vpe_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); |
| } |
| |
| static int vpe_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); |
| } |
| |
| static int vpe_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); |
| } |
| |
| static int vpe_streamon(struct file *file, void *priv, enum v4l2_buf_type type) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); |
| } |
| |
| static int vpe_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| vpe_dump_regs(ctx->dev); |
| vpdma_dump_regs(ctx->dev->vpdma); |
| |
| return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); |
| } |
| |
| /* |
| * defines number of buffers/frames a context can process with VPE before |
| * switching to a different context. default value is 1 buffer per context |
| */ |
| #define V4L2_CID_VPE_BUFS_PER_JOB (V4L2_CID_USER_TI_VPE_BASE + 0) |
| |
| static int vpe_s_ctrl(struct v4l2_ctrl *ctrl) |
| { |
| struct vpe_ctx *ctx = |
| container_of(ctrl->handler, struct vpe_ctx, hdl); |
| |
| switch (ctrl->id) { |
| case V4L2_CID_VPE_BUFS_PER_JOB: |
| ctx->bufs_per_job = ctrl->val; |
| break; |
| |
| default: |
| vpe_err(ctx->dev, "Invalid control\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct v4l2_ctrl_ops vpe_ctrl_ops = { |
| .s_ctrl = vpe_s_ctrl, |
| }; |
| |
| static const struct v4l2_ioctl_ops vpe_ioctl_ops = { |
| .vidioc_querycap = vpe_querycap, |
| |
| .vidioc_enum_fmt_vid_cap_mplane = vpe_enum_fmt, |
| .vidioc_g_fmt_vid_cap_mplane = vpe_g_fmt, |
| .vidioc_try_fmt_vid_cap_mplane = vpe_try_fmt, |
| .vidioc_s_fmt_vid_cap_mplane = vpe_s_fmt, |
| |
| .vidioc_enum_fmt_vid_out_mplane = vpe_enum_fmt, |
| .vidioc_g_fmt_vid_out_mplane = vpe_g_fmt, |
| .vidioc_try_fmt_vid_out_mplane = vpe_try_fmt, |
| .vidioc_s_fmt_vid_out_mplane = vpe_s_fmt, |
| |
| .vidioc_g_selection = vpe_g_selection, |
| .vidioc_s_selection = vpe_s_selection, |
| |
| .vidioc_reqbufs = vpe_reqbufs, |
| .vidioc_querybuf = vpe_querybuf, |
| |
| .vidioc_qbuf = vpe_qbuf, |
| .vidioc_dqbuf = vpe_dqbuf, |
| |
| .vidioc_streamon = vpe_streamon, |
| .vidioc_streamoff = vpe_streamoff, |
| .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| }; |
| |
| /* |
| * Queue operations |
| */ |
| static int vpe_queue_setup(struct vb2_queue *vq, |
| const struct v4l2_format *fmt, |
| unsigned int *nbuffers, unsigned int *nplanes, |
| unsigned int sizes[], void *alloc_ctxs[]) |
| { |
| int i; |
| struct vpe_ctx *ctx = vb2_get_drv_priv(vq); |
| struct vpe_q_data *q_data; |
| |
| q_data = get_q_data(ctx, vq->type); |
| |
| *nplanes = q_data->fmt->coplanar ? 2 : 1; |
| |
| for (i = 0; i < *nplanes; i++) { |
| sizes[i] = q_data->sizeimage[i]; |
| alloc_ctxs[i] = ctx->dev->alloc_ctx; |
| } |
| |
| vpe_dbg(ctx->dev, "get %d buffer(s) of size %d", *nbuffers, |
| sizes[VPE_LUMA]); |
| if (q_data->fmt->coplanar) |
| vpe_dbg(ctx->dev, " and %d\n", sizes[VPE_CHROMA]); |
| |
| return 0; |
| } |
| |
| static int vpe_buf_prepare(struct vb2_buffer *vb) |
| { |
| struct vpe_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| struct vpe_q_data *q_data; |
| int i, num_planes; |
| |
| vpe_dbg(ctx->dev, "type: %d\n", vb->vb2_queue->type); |
| |
| q_data = get_q_data(ctx, vb->vb2_queue->type); |
| num_planes = q_data->fmt->coplanar ? 2 : 1; |
| |
| if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| if (!(q_data->flags & Q_DATA_INTERLACED)) { |
| vb->v4l2_buf.field = V4L2_FIELD_NONE; |
| } else { |
| if (vb->v4l2_buf.field != V4L2_FIELD_TOP && |
| vb->v4l2_buf.field != V4L2_FIELD_BOTTOM) |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < num_planes; i++) { |
| if (vb2_plane_size(vb, i) < q_data->sizeimage[i]) { |
| vpe_err(ctx->dev, |
| "data will not fit into plane (%lu < %lu)\n", |
| vb2_plane_size(vb, i), |
| (long) q_data->sizeimage[i]); |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < num_planes; i++) |
| vb2_set_plane_payload(vb, i, q_data->sizeimage[i]); |
| |
| return 0; |
| } |
| |
| static void vpe_buf_queue(struct vb2_buffer *vb) |
| { |
| struct vpe_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); |
| } |
| |
| static void vpe_wait_prepare(struct vb2_queue *q) |
| { |
| struct vpe_ctx *ctx = vb2_get_drv_priv(q); |
| vpe_unlock(ctx); |
| } |
| |
| static void vpe_wait_finish(struct vb2_queue *q) |
| { |
| struct vpe_ctx *ctx = vb2_get_drv_priv(q); |
| vpe_lock(ctx); |
| } |
| |
| static struct vb2_ops vpe_qops = { |
| .queue_setup = vpe_queue_setup, |
| .buf_prepare = vpe_buf_prepare, |
| .buf_queue = vpe_buf_queue, |
| .wait_prepare = vpe_wait_prepare, |
| .wait_finish = vpe_wait_finish, |
| }; |
| |
| static int queue_init(void *priv, struct vb2_queue *src_vq, |
| struct vb2_queue *dst_vq) |
| { |
| struct vpe_ctx *ctx = priv; |
| int ret; |
| |
| memset(src_vq, 0, sizeof(*src_vq)); |
| 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 v4l2_m2m_buffer); |
| src_vq->ops = &vpe_qops; |
| src_vq->mem_ops = &vb2_dma_contig_memops; |
| src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| |
| ret = vb2_queue_init(src_vq); |
| if (ret) |
| return ret; |
| |
| memset(dst_vq, 0, sizeof(*dst_vq)); |
| 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 = &vpe_qops; |
| dst_vq->mem_ops = &vb2_dma_contig_memops; |
| dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| |
| return vb2_queue_init(dst_vq); |
| } |
| |
| static const struct v4l2_ctrl_config vpe_bufs_per_job = { |
| .ops = &vpe_ctrl_ops, |
| .id = V4L2_CID_VPE_BUFS_PER_JOB, |
| .name = "Buffers Per Transaction", |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .def = VPE_DEF_BUFS_PER_JOB, |
| .min = 1, |
| .max = VIDEO_MAX_FRAME, |
| .step = 1, |
| }; |
| |
| /* |
| * File operations |
| */ |
| static int vpe_open(struct file *file) |
| { |
| struct vpe_dev *dev = video_drvdata(file); |
| struct vpe_ctx *ctx = NULL; |
| struct vpe_q_data *s_q_data; |
| struct v4l2_ctrl_handler *hdl; |
| int ret; |
| |
| vpe_dbg(dev, "vpe_open\n"); |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| ctx->dev = dev; |
| |
| if (mutex_lock_interruptible(&dev->dev_mutex)) { |
| ret = -ERESTARTSYS; |
| goto free_ctx; |
| } |
| |
| ret = vpdma_create_desc_list(&ctx->desc_list, VPE_DESC_LIST_SIZE, |
| VPDMA_LIST_TYPE_NORMAL); |
| if (ret != 0) |
| goto unlock; |
| |
| ret = vpdma_alloc_desc_buf(&ctx->mmr_adb, sizeof(struct vpe_mmr_adb)); |
| if (ret != 0) |
| goto free_desc_list; |
| |
| ret = vpdma_alloc_desc_buf(&ctx->sc_coeff_h, SC_COEF_SRAM_SIZE); |
| if (ret != 0) |
| goto free_mmr_adb; |
| |
| ret = vpdma_alloc_desc_buf(&ctx->sc_coeff_v, SC_COEF_SRAM_SIZE); |
| if (ret != 0) |
| goto free_sc_h; |
| |
| init_adb_hdrs(ctx); |
| |
| v4l2_fh_init(&ctx->fh, video_devdata(file)); |
| file->private_data = &ctx->fh; |
| |
| hdl = &ctx->hdl; |
| v4l2_ctrl_handler_init(hdl, 1); |
| v4l2_ctrl_new_custom(hdl, &vpe_bufs_per_job, NULL); |
| if (hdl->error) { |
| ret = hdl->error; |
| goto exit_fh; |
| } |
| ctx->fh.ctrl_handler = hdl; |
| v4l2_ctrl_handler_setup(hdl); |
| |
| s_q_data = &ctx->q_data[Q_DATA_SRC]; |
| s_q_data->fmt = &vpe_formats[2]; |
| s_q_data->width = 1920; |
| s_q_data->height = 1080; |
| s_q_data->bytesperline[VPE_LUMA] = (s_q_data->width * |
| s_q_data->fmt->vpdma_fmt[VPE_LUMA]->depth) >> 3; |
| s_q_data->sizeimage[VPE_LUMA] = (s_q_data->bytesperline[VPE_LUMA] * |
| s_q_data->height); |
| s_q_data->colorspace = V4L2_COLORSPACE_REC709; |
| s_q_data->field = V4L2_FIELD_NONE; |
| s_q_data->c_rect.left = 0; |
| s_q_data->c_rect.top = 0; |
| s_q_data->c_rect.width = s_q_data->width; |
| s_q_data->c_rect.height = s_q_data->height; |
| s_q_data->flags = 0; |
| |
| ctx->q_data[Q_DATA_DST] = *s_q_data; |
| |
| set_dei_shadow_registers(ctx); |
| set_src_registers(ctx); |
| set_dst_registers(ctx); |
| ret = set_srcdst_params(ctx); |
| if (ret) |
| goto exit_fh; |
| |
| ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); |
| |
| if (IS_ERR(ctx->m2m_ctx)) { |
| ret = PTR_ERR(ctx->m2m_ctx); |
| goto exit_fh; |
| } |
| |
| v4l2_fh_add(&ctx->fh); |
| |
| /* |
| * for now, just report the creation of the first instance, we can later |
| * optimize the driver to enable or disable clocks when the first |
| * instance is created or the last instance released |
| */ |
| if (atomic_inc_return(&dev->num_instances) == 1) |
| vpe_dbg(dev, "first instance created\n"); |
| |
| ctx->bufs_per_job = VPE_DEF_BUFS_PER_JOB; |
| |
| ctx->load_mmrs = true; |
| |
| vpe_dbg(dev, "created instance %p, m2m_ctx: %p\n", |
| ctx, ctx->m2m_ctx); |
| |
| mutex_unlock(&dev->dev_mutex); |
| |
| return 0; |
| exit_fh: |
| v4l2_ctrl_handler_free(hdl); |
| v4l2_fh_exit(&ctx->fh); |
| vpdma_free_desc_buf(&ctx->sc_coeff_v); |
| free_sc_h: |
| vpdma_free_desc_buf(&ctx->sc_coeff_h); |
| free_mmr_adb: |
| vpdma_free_desc_buf(&ctx->mmr_adb); |
| free_desc_list: |
| vpdma_free_desc_list(&ctx->desc_list); |
| unlock: |
| mutex_unlock(&dev->dev_mutex); |
| free_ctx: |
| kfree(ctx); |
| return ret; |
| } |
| |
| static int vpe_release(struct file *file) |
| { |
| struct vpe_dev *dev = video_drvdata(file); |
| struct vpe_ctx *ctx = file2ctx(file); |
| |
| vpe_dbg(dev, "releasing instance %p\n", ctx); |
| |
| mutex_lock(&dev->dev_mutex); |
| free_vbs(ctx); |
| free_mv_buffers(ctx); |
| vpdma_free_desc_list(&ctx->desc_list); |
| vpdma_free_desc_buf(&ctx->mmr_adb); |
| |
| v4l2_fh_del(&ctx->fh); |
| v4l2_fh_exit(&ctx->fh); |
| v4l2_ctrl_handler_free(&ctx->hdl); |
| v4l2_m2m_ctx_release(ctx->m2m_ctx); |
| |
| kfree(ctx); |
| |
| /* |
| * for now, just report the release of the last instance, we can later |
| * optimize the driver to enable or disable clocks when the first |
| * instance is created or the last instance released |
| */ |
| if (atomic_dec_return(&dev->num_instances) == 0) |
| vpe_dbg(dev, "last instance released\n"); |
| |
| mutex_unlock(&dev->dev_mutex); |
| |
| return 0; |
| } |
| |
| static unsigned int vpe_poll(struct file *file, |
| struct poll_table_struct *wait) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vpe_dev *dev = ctx->dev; |
| int ret; |
| |
| mutex_lock(&dev->dev_mutex); |
| ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); |
| mutex_unlock(&dev->dev_mutex); |
| return ret; |
| } |
| |
| static int vpe_mmap(struct file *file, struct vm_area_struct *vma) |
| { |
| struct vpe_ctx *ctx = file2ctx(file); |
| struct vpe_dev *dev = ctx->dev; |
| int ret; |
| |
| if (mutex_lock_interruptible(&dev->dev_mutex)) |
| return -ERESTARTSYS; |
| ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); |
| mutex_unlock(&dev->dev_mutex); |
| return ret; |
| } |
| |
| static const struct v4l2_file_operations vpe_fops = { |
| .owner = THIS_MODULE, |
| .open = vpe_open, |
| .release = vpe_release, |
| .poll = vpe_poll, |
| .unlocked_ioctl = video_ioctl2, |
| .mmap = vpe_mmap, |
| }; |
| |
| static struct video_device vpe_videodev = { |
| .name = VPE_MODULE_NAME, |
| .fops = &vpe_fops, |
| .ioctl_ops = &vpe_ioctl_ops, |
| .minor = -1, |
| .release = video_device_release_empty, |
| .vfl_dir = VFL_DIR_M2M, |
| }; |
| |
| static struct v4l2_m2m_ops m2m_ops = { |
| .device_run = device_run, |
| .job_ready = job_ready, |
| .job_abort = job_abort, |
| .lock = vpe_lock, |
| .unlock = vpe_unlock, |
| }; |
| |
| static int vpe_runtime_get(struct platform_device *pdev) |
| { |
| int r; |
| |
| dev_dbg(&pdev->dev, "vpe_runtime_get\n"); |
| |
| r = pm_runtime_get_sync(&pdev->dev); |
| WARN_ON(r < 0); |
| return r < 0 ? r : 0; |
| } |
| |
| static void vpe_runtime_put(struct platform_device *pdev) |
| { |
| |
| int r; |
| |
| dev_dbg(&pdev->dev, "vpe_runtime_put\n"); |
| |
| r = pm_runtime_put_sync(&pdev->dev); |
| WARN_ON(r < 0 && r != -ENOSYS); |
| } |
| |
| static void vpe_fw_cb(struct platform_device *pdev) |
| { |
| struct vpe_dev *dev = platform_get_drvdata(pdev); |
| struct video_device *vfd; |
| int ret; |
| |
| vfd = &dev->vfd; |
| *vfd = vpe_videodev; |
| vfd->lock = &dev->dev_mutex; |
| vfd->v4l2_dev = &dev->v4l2_dev; |
| |
| ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); |
| if (ret) { |
| vpe_err(dev, "Failed to register video device\n"); |
| |
| vpe_set_clock_enable(dev, 0); |
| vpe_runtime_put(pdev); |
| pm_runtime_disable(&pdev->dev); |
| v4l2_m2m_release(dev->m2m_dev); |
| vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); |
| v4l2_device_unregister(&dev->v4l2_dev); |
| |
| return; |
| } |
| |
| video_set_drvdata(vfd, dev); |
| snprintf(vfd->name, sizeof(vfd->name), "%s", vpe_videodev.name); |
| dev_info(dev->v4l2_dev.dev, "Device registered as /dev/video%d\n", |
| vfd->num); |
| } |
| |
| static int vpe_probe(struct platform_device *pdev) |
| { |
| struct vpe_dev *dev; |
| int ret, irq, func; |
| |
| dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| |
| spin_lock_init(&dev->lock); |
| |
| ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); |
| if (ret) |
| return ret; |
| |
| atomic_set(&dev->num_instances, 0); |
| mutex_init(&dev->dev_mutex); |
| |
| dev->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
| "vpe_top"); |
| /* |
| * HACK: we get resource info from device tree in the form of a list of |
| * VPE sub blocks, the driver currently uses only the base of vpe_top |
| * for register access, the driver should be changed later to access |
| * registers based on the sub block base addresses |
| */ |
| dev->base = devm_ioremap(&pdev->dev, dev->res->start, SZ_32K); |
| if (!dev->base) { |
| ret = -ENOMEM; |
| goto v4l2_dev_unreg; |
| } |
| |
| irq = platform_get_irq(pdev, 0); |
| ret = devm_request_irq(&pdev->dev, irq, vpe_irq, 0, VPE_MODULE_NAME, |
| dev); |
| if (ret) |
| goto v4l2_dev_unreg; |
| |
| platform_set_drvdata(pdev, dev); |
| |
| dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); |
| if (IS_ERR(dev->alloc_ctx)) { |
| vpe_err(dev, "Failed to alloc vb2 context\n"); |
| ret = PTR_ERR(dev->alloc_ctx); |
| goto v4l2_dev_unreg; |
| } |
| |
| dev->m2m_dev = v4l2_m2m_init(&m2m_ops); |
| if (IS_ERR(dev->m2m_dev)) { |
| vpe_err(dev, "Failed to init mem2mem device\n"); |
| ret = PTR_ERR(dev->m2m_dev); |
| goto rel_ctx; |
| } |
| |
| pm_runtime_enable(&pdev->dev); |
| |
| ret = vpe_runtime_get(pdev); |
| if (ret) |
| goto rel_m2m; |
| |
| /* Perform clk enable followed by reset */ |
| vpe_set_clock_enable(dev, 1); |
| |
| vpe_top_reset(dev); |
| |
| func = read_field_reg(dev, VPE_PID, VPE_PID_FUNC_MASK, |
| VPE_PID_FUNC_SHIFT); |
| vpe_dbg(dev, "VPE PID function %x\n", func); |
| |
| vpe_top_vpdma_reset(dev); |
| |
| dev->sc = sc_create(pdev); |
| if (IS_ERR(dev->sc)) { |
| ret = PTR_ERR(dev->sc); |
| goto runtime_put; |
| } |
| |
| dev->csc = csc_create(pdev); |
| if (IS_ERR(dev->csc)) { |
| ret = PTR_ERR(dev->csc); |
| goto runtime_put; |
| } |
| |
| dev->vpdma = vpdma_create(pdev, vpe_fw_cb); |
| if (IS_ERR(dev->vpdma)) { |
| ret = PTR_ERR(dev->vpdma); |
| goto runtime_put; |
| } |
| |
| return 0; |
| |
| runtime_put: |
| vpe_runtime_put(pdev); |
| rel_m2m: |
| pm_runtime_disable(&pdev->dev); |
| v4l2_m2m_release(dev->m2m_dev); |
| rel_ctx: |
| vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); |
| v4l2_dev_unreg: |
| v4l2_device_unregister(&dev->v4l2_dev); |
| |
| return ret; |
| } |
| |
| static int vpe_remove(struct platform_device *pdev) |
| { |
| struct vpe_dev *dev = |
| (struct vpe_dev *) platform_get_drvdata(pdev); |
| |
| v4l2_info(&dev->v4l2_dev, "Removing " VPE_MODULE_NAME); |
| |
| v4l2_m2m_release(dev->m2m_dev); |
| video_unregister_device(&dev->vfd); |
| v4l2_device_unregister(&dev->v4l2_dev); |
| vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); |
| |
| vpe_set_clock_enable(dev, 0); |
| vpe_runtime_put(pdev); |
| pm_runtime_disable(&pdev->dev); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_OF) |
| static const struct of_device_id vpe_of_match[] = { |
| { |
| .compatible = "ti,vpe", |
| }, |
| {}, |
| }; |
| #else |
| #define vpe_of_match NULL |
| #endif |
| |
| static struct platform_driver vpe_pdrv = { |
| .probe = vpe_probe, |
| .remove = vpe_remove, |
| .driver = { |
| .name = VPE_MODULE_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = vpe_of_match, |
| }, |
| }; |
| |
| module_platform_driver(vpe_pdrv); |
| |
| MODULE_DESCRIPTION("TI VPE driver"); |
| MODULE_AUTHOR("Dale Farnsworth, <dale@farnsworth.org>"); |
| MODULE_LICENSE("GPL"); |