Messages in this thread | | | Date | Mon, 15 Dec 2014 10:23:42 -0800 | From | Steve Longerbeam <> | Subject | Re: i.MX6 CSC and scaler |
| |
On 12/15/2014 09:03 AM, Jean-Michel Hautbois wrote: > Hi Steve, Philipp, > > I see in the kernel sources you created a ipu-ic.c file which helps > working with the Color Space Converter on i.MX6. > We would like to use it on our board with GStreamer, as the conversion > done by the CPU is not very efficient :). > What is the easiest/best approach to this ? > Should we create a video device using a new driver, which would be > instanciated as a /dev/videoX and see as a transform element in > GStreamer ? > The idea is to have a separate element, ideally without memory copy or > anything like that.
Hi JM,
I've written a mem2mem driver for this. It does tiling to support > 1024x1024 scaled output frames. I've attached it, feel free to use it, it was pulled from a 3.14 kernel. I haven't submitted this driver to community yet, mostly because I want to improve it, most importantly move the tiling support into ipu-ic, so that tiling could eventually be used by any media-device enabled pipeline elements (instead of only mem2mem). Also Philipp probably also has a mem2mem device as well.
You would need to write a gstreamer plugin to make use of the mem2mem device for h/w CSC and scaling.
Steve
/* * This is a mem2mem driver for the Freescale i.MX6 SOC. It carries out * color-space conversion, downsizing, resizing, and rotation transformations * on input buffers using the IPU Image Converter's Post-Processing task. * * Based on mem2mem_testdev.c by Pawel Osciak. * * Copyright (c) 2012-2013 Mentor Graphics Inc. * Steve Longerbeam <steve_longerbeam@mentor.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the * License, or (at your option) any later version */ #include <linux/module.h> #include <linux/delay.h> #include <linux/fs.h> #include <linux/timer.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/log2.h>
#include <linux/platform_device.h> #include <linux/interrupt.h> #include <media/v4l2-mem2mem.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/videobuf2-dma-contig.h> #include <video/imx-ipu-v3.h> #include <media/imx6.h>
MODULE_DESCRIPTION("i.MX6 Post-Processing mem2mem device"); MODULE_AUTHOR("Steve Longerbeam <steve_longerbeam@mentor.com>"); MODULE_LICENSE("GPL"); MODULE_VERSION("0.1");
static int instrument; module_param(instrument, int, 0); MODULE_PARM_DESC(instrument, "1 = enable conversion time measurement");
#define MIN_W 128 #define MIN_H 128 #define MAX_W 4096 #define MAX_H 4096 #define S_ALIGN 1 /* multiple of 2 pixels */
/* * The IC Resizer has a restriction that the output frame from the * resizer must be 1024 or less in both width (pixels) and height * (lines). * * This driver attempts to split up a conversion when the desired * capture (output) frame resolution exceeds the IC resizer limit * of 1024 in either dimension. * * If either dimension of the output frame exceeds the limit, the * dimension is split into 1, 2, or 4 equal stripes, for a maximum * of 4*4 or 16 segments. A conversion is then carried out for each * segment (but taking care to pass the full frame stride length to * the DMA channel's parameter memory!). IDMA double-buffering is used * to convert each segment back-to-back when possible (see note below * when double_buffering boolean is set). * * Note that the input frame must be split up into the same number * of segments as the output frame. */ #define MAX_SEG_W 4 #define MAX_SEG_H 4 #define MAX_SEGMENTS (MAX_SEG_W * MAX_SEG_H)
/* Flags that indicate a format can be used for capture/output */ #define MEM2MEM_CAPTURE (1 << 0) #define MEM2MEM_OUTPUT (1 << 1)
#define MEM2MEM_NAME "mx6-m2m"
/* Per queue */ #define MEM2MEM_DEF_NUM_BUFS VIDEO_MAX_FRAME /* In bytes, per queue */ #define MEM2MEM_VID_MEM_LIMIT SZ_256M
#define dprintk(dev, fmt, arg...) \ v4l2_dbg(1, 1, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg)
struct m2mx6_pixfmt { char *name; u32 fourcc; int depth; /* total bpp */ int y_depth; /* depth of Y plane for planar formats */ int uv_width_dec; /* decimation in width for U/V planes */ int uv_height_dec; /* decimation in height for U/V planes */ bool uv_packed; /* partial planar (U and V in same plane) */ u32 types; /* Types the format can be used for */ };
struct m2mx6_seg_off { /* start Y or packed offset of this segment */ u32 offset; /* offset from start to segment in U plane, for planar formats */ u32 u_off; /* offset from start to segment in V plane, for planar formats */ u32 v_off; };
/* Per-queue, driver-specific private data */ struct m2mx6_q_data { unsigned int width; unsigned int height; unsigned int bytesperline; unsigned int stride; unsigned int rot_stride; unsigned int sizeimage; struct m2mx6_pixfmt *fmt;
dma_addr_t phys_start; struct m2mx6_seg_off seg_off[MAX_SEGMENTS]; /* width of each segment */ unsigned int seg_width; /* height of each segment */ unsigned int seg_height; };
struct m2mx6_dev { struct v4l2_device v4l2_dev; struct video_device *vfd;
struct mutex dev_mutex; spinlock_t irqlock;
struct ipu_soc *ipu;
struct v4l2_m2m_dev *m2m_dev; struct vb2_alloc_ctx *alloc_ctx; };
struct m2mx6_ctx { struct m2mx6_dev *dev;
/* the IPU resources this context will need */ struct ipuv3_channel *ipu_mem_pp_ch; struct ipuv3_channel *ipu_pp_mem_ch; struct ipuv3_channel *ipu_mem_rot_pp_ch; struct ipuv3_channel *ipu_rot_pp_mem_ch; struct ipu_ic *ic;
/* the IPU end-of-frame irqs */ int pp_mem_irq; int rot_pp_mem_irq; /* current buffer number for double buffering */ int cur_buf_num;
/* intermediate buffer for rotation */ void *rot_intermediate_buf[2]; dma_addr_t rot_intermediate_phys[2]; unsigned long rot_intermediate_buf_size;
/* The rotation controls */ int rotation; /* degrees */ bool hflip; bool vflip;
/* derived from rotation, hflip, vflip controls */ enum ipu_rotate_mode rot_mode;
/* Abort requested by m2m */ int aborting;
struct v4l2_m2m_ctx *m2m_ctx;
/* Source and destination queue data */ struct m2mx6_q_data q_data[2];
/* can we use double-buffering for this operation? */ bool double_buffering; /* # of rows (horizontal stripes) if dest height is > 1024 */ unsigned int num_rows; /* # of columns (vertical stripes) if dest width is > 1024 */ unsigned int num_cols; /* num_rows * num_cols */ unsigned int num_segs; /* Next segment to process */ unsigned int next_seg;
/* for instrumenting */ struct timeval start; };
enum { V4L2_M2M_SRC = 0, V4L2_M2M_DST = 1, };
static struct m2mx6_pixfmt m2mx6_formats[] = { { .name = "RGB565", .fourcc = V4L2_PIX_FMT_RGB565, .depth = 16, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "RGB24", .fourcc = V4L2_PIX_FMT_RGB24, .depth = 24, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "BGR24", .fourcc = V4L2_PIX_FMT_BGR24, .depth = 24, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "RGB32", .fourcc = V4L2_PIX_FMT_RGB32, .depth = 32, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "BGR32", .fourcc = V4L2_PIX_FMT_BGR32, .depth = 32, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:2 packed, YUYV", .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, .uv_width_dec = 2, .uv_height_dec = 1, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:2 packed, UYVY", .fourcc = V4L2_PIX_FMT_UYVY, .depth = 16, .uv_width_dec = 2, .uv_height_dec = 1, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:0 planar, YUV", .fourcc = V4L2_PIX_FMT_YUV420, .depth = 12, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 2, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:0 planar, YVU", .fourcc = V4L2_PIX_FMT_YVU420, .depth = 12, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 2, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:0 partial planar, NV12", .fourcc = V4L2_PIX_FMT_NV12, .depth = 12, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 2, .uv_packed = true, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:0 partial planar, NV21", .fourcc = V4L2_PIX_FMT_NV21, .depth = 12, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 2, .uv_packed = true, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:2 planar, YUV", .fourcc = V4L2_PIX_FMT_YUV422P, .depth = 16, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 1, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:2 partial planar, NV16", .fourcc = V4L2_PIX_FMT_NV16, .depth = 16, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 1, .uv_packed = true, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, { .name = "4:2:2 partial planar, NV61", .fourcc = V4L2_PIX_FMT_NV61, .depth = 16, .y_depth = 8, .uv_width_dec = 2, .uv_height_dec = 1, .uv_packed = true, .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, }, };
#define NUM_FORMATS ARRAY_SIZE(m2mx6_formats)
/* Rotation controls */ static struct v4l2_queryctrl m2mx6_ctrls[] = { { .id = V4L2_CID_HFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Horizontal Flip", .minimum = 0, .maximum = 1, .step = 1, .default_value = 0, }, { .id = V4L2_CID_VFLIP, .type = V4L2_CTRL_TYPE_BOOLEAN, .name = "Vertical Flip", .minimum = 0, .maximum = 1, .step = 1, .default_value = 0, }, { .id = V4L2_CID_ROTATE, .type = V4L2_CTRL_TYPE_INTEGER, .name = "Rotation", .minimum = 0, .maximum = 270, .step = 90, .default_value = 0, }, };
static struct m2mx6_pixfmt *m2mx6_get_format(struct v4l2_format *f) { struct m2mx6_pixfmt *ret = NULL; int i;
for (i = 0; i < NUM_FORMATS; i++) { if (m2mx6_formats[i].fourcc == f->fmt.pix.pixelformat) { ret = &m2mx6_formats[i]; break; } }
return ret; }
static struct v4l2_queryctrl *m2mx6_get_ctrl(int id) { struct v4l2_queryctrl *ret = NULL; int i;
for (i = 0; i < ARRAY_SIZE(m2mx6_ctrls); i++) { if (id == m2mx6_ctrls[i].id) { ret = &m2mx6_ctrls[i]; break; } }
return ret; }
static struct m2mx6_q_data *get_q_data(struct m2mx6_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; }
static inline int m2mx6_num_stripes(int dim) { if (dim <= 1024) return 1; else if (dim <= 2048) return 2; else return 4; }
static void m2mx6_calc_seg_offsets_planar(struct m2mx6_ctx *ctx, enum v4l2_buf_type type) { struct m2mx6_q_data *q_data = get_q_data(ctx, type); struct m2mx6_pixfmt *fmt = q_data->fmt; u32 W, H, w, h, y_depth, y_stride, uv_stride; u32 uv_row_off, uv_col_off, uv_off, u_off, v_off; u32 y_row_off, y_col_off, y_off; u32 y_size, uv_size; int row, col, seg = 0;
/* setup some convenience vars */ W = q_data->width; H = q_data->height; w = q_data->seg_width; h = q_data->seg_height;
y_depth = fmt->y_depth; y_stride = q_data->stride; uv_stride = y_stride / fmt->uv_width_dec; if (fmt->uv_packed) uv_stride *= 2;
y_size = H * y_stride; uv_size = y_size / (fmt->uv_width_dec * fmt->uv_height_dec);
for (row = 0; row < ctx->num_rows; row++) { y_row_off = row * h * y_stride; uv_row_off = (row * h * uv_stride) / fmt->uv_height_dec;
for (col = 0; col < ctx->num_cols; col++) { y_col_off = (col * w * y_depth) >> 3; uv_col_off = y_col_off / fmt->uv_width_dec; if (fmt->uv_packed) uv_col_off *= 2;
y_off = y_row_off + y_col_off; uv_off = uv_row_off + uv_col_off;
u_off = y_size - y_off + uv_off; v_off = (fmt->uv_packed) ? 0 : u_off + uv_size;
q_data->seg_off[seg].offset = y_off; q_data->seg_off[seg].u_off = u_off; q_data->seg_off[seg++].v_off = v_off;
dprintk(ctx->dev, "%s@[%d,%d]: y_off %08x, u_off %08x, v_off %08x\n", type == V4L2_BUF_TYPE_VIDEO_OUTPUT ? "Output" : "Capture", row, col, y_off, u_off, v_off); } } }
static void m2mx6_calc_seg_offsets_packed(struct m2mx6_ctx *ctx, enum v4l2_buf_type type) { struct m2mx6_q_data *q_data = get_q_data(ctx, type); struct m2mx6_pixfmt *fmt = q_data->fmt; u32 W, H, w, h, depth, stride; u32 row_off, col_off; int row, col, seg = 0;
/* setup some convenience vars */ W = q_data->width; H = q_data->height; w = q_data->seg_width; h = q_data->seg_height; stride = q_data->stride; depth = fmt->depth;
for (row = 0; row < ctx->num_rows; row++) { row_off = row * h * stride;
for (col = 0; col < ctx->num_cols; col++) { col_off = (col * w * depth) >> 3;
q_data->seg_off[seg].offset = row_off + col_off; q_data->seg_off[seg].u_off = 0; q_data->seg_off[seg++].v_off = 0;
dprintk(ctx->dev, "%s@[%d,%d]: phys %08x\n", type == V4L2_BUF_TYPE_VIDEO_OUTPUT ? "Output" : "Capture", row, col, row_off + col_off); } } }
static void m2mx6_calc_seg_offsets(struct m2mx6_ctx *ctx, enum v4l2_buf_type type) { struct m2mx6_q_data *q_data = get_q_data(ctx, type);
memset(q_data->seg_off, 0, sizeof(q_data->seg_off));
if (q_data->fmt->y_depth) m2mx6_calc_seg_offsets_planar(ctx, type); else m2mx6_calc_seg_offsets_packed(ctx, type); }
/* * mem2mem callbacks */
static void m2mx6_job_abort(void *priv) { struct m2mx6_ctx *ctx = priv;
/* Will cancel the transaction in the next interrupt handler */ ctx->aborting = 1; }
static void m2mx6_lock(void *priv) { struct m2mx6_ctx *ctx = priv; struct m2mx6_dev *dev = ctx->dev; mutex_lock(&dev->dev_mutex); }
static void m2mx6_unlock(void *priv) { struct m2mx6_ctx *ctx = priv; struct m2mx6_dev *dev = ctx->dev; mutex_unlock(&dev->dev_mutex); }
/* hold spinlock when calling */ static void m2mx6_norotate_stop(struct m2mx6_ctx *ctx) { /* disable IC tasks and the channels */ ipu_ic_task_disable(ctx->ic); ipu_idmac_disable_channel(ctx->ipu_mem_pp_ch); ipu_idmac_disable_channel(ctx->ipu_pp_mem_ch);
ipu_ic_disable(ctx->ic); }
/* hold spinlock when calling */ static void m2mx6_rotate_stop(struct m2mx6_ctx *ctx) { /* disable IC tasks and the channels */ ipu_ic_task_disable(ctx->ic);
ipu_idmac_disable_channel(ctx->ipu_mem_pp_ch); ipu_idmac_disable_channel(ctx->ipu_pp_mem_ch); ipu_idmac_disable_channel(ctx->ipu_mem_rot_pp_ch); ipu_idmac_disable_channel(ctx->ipu_rot_pp_mem_ch);
ipu_idmac_unlink(ctx->ipu_pp_mem_ch, ctx->ipu_mem_rot_pp_ch);
ipu_ic_disable(ctx->ic); }
/* hold spinlock when calling */ static void init_idmac_channel(struct m2mx6_ctx *ctx, struct ipuv3_channel *channel, struct m2mx6_q_data *q_data, enum ipu_rotate_mode rot_mode, bool rot_swap_width_height) { unsigned int burst_size; u32 width, height, stride; dma_addr_t addr0, addr1 = 0; struct ipu_image image;
if (rot_swap_width_height) { width = q_data->seg_height; height = q_data->seg_width; stride = q_data->rot_stride; addr0 = ctx->rot_intermediate_phys[0]; if (ctx->double_buffering) addr1 = ctx->rot_intermediate_phys[1]; } else { width = q_data->seg_width; height = q_data->seg_height; stride = q_data->stride; addr0 = q_data->phys_start + q_data->seg_off[0].offset; if (ctx->double_buffering) addr1 = q_data->phys_start + q_data->seg_off[1].offset; }
ipu_cpmem_zero(channel);
memset(&image, 0, sizeof(image)); image.pix.width = image.rect.width = width; image.pix.height = image.rect.height = height; image.pix.bytesperline = stride; image.pix.pixelformat = q_data->fmt->fourcc; image.phys0 = addr0; image.phys1 = addr1; ipu_cpmem_set_image(channel, &image);
ipu_cpmem_set_uv_offset(channel, q_data->seg_off[0].u_off, q_data->seg_off[0].v_off);
if (rot_mode) ipu_cpmem_set_rotation(channel, rot_mode);
if (channel == ctx->ipu_mem_rot_pp_ch || channel == ctx->ipu_rot_pp_mem_ch) { burst_size = 8; ipu_cpmem_set_block_mode(channel); } else burst_size = (width % 16) ? 8 : 16;
ipu_cpmem_set_burstsize(channel, burst_size);
ipu_ic_task_idma_init(ctx->ic, channel, width, height, burst_size, rot_mode);
ipu_cpmem_set_axi_id(channel, 1); ipu_idmac_lock_enable(channel, 8);
ipu_idmac_set_double_buffer(channel, ctx->double_buffering); }
/* hold spinlock when calling */ static void m2mx6_norotate_start(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *s_q_data, *d_q_data; enum ipu_color_space src_cs, dest_cs; int ret;
s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
src_cs = ipu_pixelformat_to_colorspace(s_q_data->fmt->fourcc); dest_cs = ipu_pixelformat_to_colorspace(d_q_data->fmt->fourcc);
/* setup the IC resizer and CSC */ ret = ipu_ic_task_init(ctx->ic, s_q_data->seg_width, s_q_data->seg_height, d_q_data->seg_width, d_q_data->seg_height, src_cs, dest_cs); if (ret) { v4l2_err(&dev->v4l2_dev, "ipu_ic_task_init failed, %d\n", ret); return; }
/* init the source MEM-->IC PP IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_mem_pp_ch, s_q_data, IPU_ROTATE_NONE, false);
/* init the destination IC PP-->MEM IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_pp_mem_ch, d_q_data, ctx->rot_mode, false);
/* enable the IC */ ipu_ic_enable(ctx->ic);
/* set buffers ready */ ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, 0); ipu_idmac_select_buffer(ctx->ipu_pp_mem_ch, 0); if (ctx->double_buffering) { ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, 1); ipu_idmac_select_buffer(ctx->ipu_pp_mem_ch, 1); }
/* enable the channels! */ ipu_idmac_enable_channel(ctx->ipu_mem_pp_ch); ipu_idmac_enable_channel(ctx->ipu_pp_mem_ch);
ipu_ic_task_enable(ctx->ic);
ipu_cpmem_dump(ctx->ipu_mem_pp_ch); ipu_cpmem_dump(ctx->ipu_pp_mem_ch); ipu_ic_dump(ctx->ic); ipu_dump(dev->ipu); }
/* hold spinlock when calling */ static void m2mx6_rotate_start(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *s_q_data, *d_q_data; enum ipu_color_space src_cs, dest_cs; int ret;
s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
src_cs = ipu_pixelformat_to_colorspace(s_q_data->fmt->fourcc); dest_cs = ipu_pixelformat_to_colorspace(d_q_data->fmt->fourcc);
/* setup the IC resizer and CSC (swap output width/height) */ ret = ipu_ic_task_init(ctx->ic, s_q_data->seg_width, s_q_data->seg_height, d_q_data->seg_height, d_q_data->seg_width, src_cs, dest_cs); if (ret) { v4l2_err(&dev->v4l2_dev, "ipu_ic_task_init failed, %d\n", ret); return; }
/* init the source MEM-->IC PP IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_mem_pp_ch, s_q_data, IPU_ROTATE_NONE, false);
/* init the IC PP-->MEM IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_pp_mem_ch, d_q_data, IPU_ROTATE_NONE, true);
/* init the MEM-->IC PP ROT IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_mem_rot_pp_ch, d_q_data, ctx->rot_mode, true);
/* init the destination IC PP ROT-->MEM IDMAC channel */ init_idmac_channel(ctx, ctx->ipu_rot_pp_mem_ch, d_q_data, IPU_ROTATE_NONE, false);
/* now link IC PP-->MEM to MEM-->IC PP ROT */ ipu_idmac_link(ctx->ipu_pp_mem_ch, ctx->ipu_mem_rot_pp_ch);
/* enable the IC */ ipu_ic_enable(ctx->ic);
/* set buffers ready */ ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, 0); ipu_idmac_select_buffer(ctx->ipu_pp_mem_ch, 0); ipu_idmac_select_buffer(ctx->ipu_rot_pp_mem_ch, 0); if (ctx->double_buffering) { ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, 1); ipu_idmac_select_buffer(ctx->ipu_pp_mem_ch, 1); ipu_idmac_select_buffer(ctx->ipu_rot_pp_mem_ch, 1); }
/* enable the channels! */ ipu_idmac_enable_channel(ctx->ipu_mem_pp_ch); ipu_idmac_enable_channel(ctx->ipu_pp_mem_ch); ipu_idmac_enable_channel(ctx->ipu_mem_rot_pp_ch); ipu_idmac_enable_channel(ctx->ipu_rot_pp_mem_ch);
ipu_ic_task_enable(ctx->ic);
ipu_cpmem_dump(ctx->ipu_mem_pp_ch); ipu_cpmem_dump(ctx->ipu_pp_mem_ch); ipu_cpmem_dump(ctx->ipu_mem_rot_pp_ch); ipu_cpmem_dump(ctx->ipu_rot_pp_mem_ch); ipu_ic_dump(ctx->ic); ipu_dump(dev->ipu); }
static void m2mx6_device_run(void *priv) { struct m2mx6_ctx *ctx = priv; struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *s_q_data, *d_q_data; struct vb2_buffer *src_buf, *dst_buf; unsigned long flags;
src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
s_q_data->phys_start = vb2_dma_contig_plane_dma_addr(src_buf, 0); d_q_data->phys_start = vb2_dma_contig_plane_dma_addr(dst_buf, 0); if (!s_q_data->phys_start || !d_q_data->phys_start) { v4l2_err(&dev->v4l2_dev, "Acquiring kernel pointers to buffers failed\n"); return; }
spin_lock_irqsave(&dev->irqlock, flags);
if (instrument) do_gettimeofday(&ctx->start);
/* * Can we use double-buffering for this operation? If there is * only one segment (the whole image can be converted in a single * operation) there's no point in using double-buffering. Also, * the IPU's IDMAC channels allow only a single U and V plane * offset shared between both buffers, but these offsets change * for every segment, and therefore would have to be updated for * each buffer which is not possible. So double-buffering is * impossible when either the source or destination images are * a planar format (YUV420, YUV422P, etc.). */ ctx->double_buffering = (ctx->num_segs > 1 && !s_q_data->fmt->y_depth && !d_q_data->fmt->y_depth);
ctx->cur_buf_num = 0; ctx->next_seg = 1;
if (ctx->rot_mode >= IPU_ROTATE_90_RIGHT) m2mx6_rotate_start(ctx); else m2mx6_norotate_start(ctx);
spin_unlock_irqrestore(&dev->irqlock, flags); }
/* hold spinlock when calling */ static bool m2mx6_doirq(struct m2mx6_ctx *ctx) { struct m2mx6_q_data *s_q_data, *d_q_data; struct vb2_buffer *src_vb, *dst_vb; struct ipuv3_channel *outch; struct m2mx6_seg_off *src_off, *dst_off;
outch = (ctx->rot_mode >= IPU_ROTATE_90_RIGHT) ? ctx->ipu_rot_pp_mem_ch : ctx->ipu_pp_mem_ch;
s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
/* * It is difficult to stop the channel DMA before the channels * enter the paused state. Without double-buffering the channels * are always in a paused state when the EOF irq occurs, so it * is safe to stop the channels now. For double-buffering we * just ignore the abort until the operation completes, when it * is safe to shut down. */ if (ctx->aborting && !ctx->double_buffering) { if (ctx->rot_mode >= IPU_ROTATE_90_RIGHT) m2mx6_rotate_stop(ctx); else m2mx6_norotate_stop(ctx); return true; }
if (ctx->next_seg == ctx->num_segs) { /* * the conversion is complete */ if (ctx->rot_mode >= IPU_ROTATE_90_RIGHT) m2mx6_rotate_stop(ctx); else m2mx6_norotate_stop(ctx);
if (!ctx->aborting) { src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE);
if (instrument) { struct timeval stop; do_gettimeofday(&stop); stop.tv_sec -= ctx->start.tv_sec; stop.tv_usec -= ctx->start.tv_usec; if (stop.tv_usec < 0) { stop.tv_usec += 1000 * 1000; stop.tv_sec -= 1; } v4l2_info(&ctx->dev->v4l2_dev, "buf%d completed in %lu usec\n", dst_vb->v4l2_buf.index, stop.tv_sec * 1000 * 1000 + stop.tv_usec); } }
return true; }
/* * not done, place the next segment buffers. */ if (!ctx->double_buffering) {
src_off = &s_q_data->seg_off[ctx->next_seg]; dst_off = &d_q_data->seg_off[ctx->next_seg];
ipu_cpmem_set_buffer(ctx->ipu_mem_pp_ch, 0, s_q_data->phys_start + src_off->offset); ipu_cpmem_set_buffer(outch, 0, d_q_data->phys_start + dst_off->offset); ipu_cpmem_set_uv_offset(ctx->ipu_mem_pp_ch, src_off->u_off, src_off->v_off); ipu_cpmem_set_uv_offset(outch, dst_off->u_off, dst_off->v_off);
ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, 0); ipu_idmac_select_buffer(outch, 0);
} else if (ctx->next_seg < ctx->num_segs - 1) {
src_off = &s_q_data->seg_off[ctx->next_seg + 1]; dst_off = &d_q_data->seg_off[ctx->next_seg + 1];
ipu_cpmem_set_buffer(ctx->ipu_mem_pp_ch, ctx->cur_buf_num, s_q_data->phys_start + src_off->offset); ipu_cpmem_set_buffer(outch, ctx->cur_buf_num, d_q_data->phys_start + dst_off->offset);
ipu_idmac_select_buffer(ctx->ipu_mem_pp_ch, ctx->cur_buf_num); ipu_idmac_select_buffer(outch, ctx->cur_buf_num);
ctx->cur_buf_num ^= 1; }
ctx->next_seg++; return false; }
static irqreturn_t m2mx6_norotate_irq(int irq, void *data) { struct m2mx6_dev *dev = (struct m2mx6_dev *)data; struct m2mx6_ctx *curr_ctx; unsigned long flags; bool done;
spin_lock_irqsave(&dev->irqlock, flags);
curr_ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); if (curr_ctx == NULL) { v4l2_err(&dev->v4l2_dev, "Instance released before the end of transaction\n"); spin_unlock_irqrestore(&dev->irqlock, flags); return IRQ_HANDLED; }
if (curr_ctx->rot_mode >= IPU_ROTATE_90_RIGHT) { /* this is a rotation operation, just ignore */ spin_unlock_irqrestore(&dev->irqlock, flags); return IRQ_HANDLED; }
done = m2mx6_doirq(curr_ctx);
spin_unlock_irqrestore(&dev->irqlock, flags);
if (done) v4l2_m2m_job_finish(dev->m2m_dev, curr_ctx->m2m_ctx);
return IRQ_HANDLED; }
static irqreturn_t m2mx6_rotate_irq(int irq, void *data) { struct m2mx6_dev *dev = (struct m2mx6_dev *)data; struct m2mx6_ctx *curr_ctx; unsigned long flags; bool done;
spin_lock_irqsave(&dev->irqlock, flags);
curr_ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); if (curr_ctx == NULL) { v4l2_err(&dev->v4l2_dev, "Instance released before the end of transaction\n"); spin_unlock_irqrestore(&dev->irqlock, flags); return IRQ_HANDLED; }
if (curr_ctx->rot_mode < IPU_ROTATE_90_RIGHT) { /* this was NOT a rotation operation, shouldn't happen */ v4l2_err(&dev->v4l2_dev, "Unexpected rotation interrupt\n"); spin_unlock_irqrestore(&dev->irqlock, flags); return IRQ_HANDLED; }
done = m2mx6_doirq(curr_ctx);
spin_unlock_irqrestore(&dev->irqlock, flags);
if (done) v4l2_m2m_job_finish(dev->m2m_dev, curr_ctx->m2m_ctx);
return IRQ_HANDLED; }
static void m2mx6_release_ipu_resources(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev;
if (ctx->pp_mem_irq >= 0) devm_free_irq(dev->v4l2_dev.dev, ctx->pp_mem_irq, dev); if (ctx->rot_pp_mem_irq >= 0) devm_free_irq(dev->v4l2_dev.dev, ctx->rot_pp_mem_irq, dev);
if (!IS_ERR_OR_NULL(ctx->ipu_mem_pp_ch)) ipu_idmac_put(ctx->ipu_mem_pp_ch); if (!IS_ERR_OR_NULL(ctx->ipu_pp_mem_ch)) ipu_idmac_put(ctx->ipu_pp_mem_ch); if (!IS_ERR_OR_NULL(ctx->ipu_mem_rot_pp_ch)) ipu_idmac_put(ctx->ipu_mem_rot_pp_ch); if (!IS_ERR_OR_NULL(ctx->ipu_rot_pp_mem_ch)) ipu_idmac_put(ctx->ipu_rot_pp_mem_ch);
if (!IS_ERR_OR_NULL(ctx->ic)) ipu_ic_put(ctx->ic);
ctx->ipu_mem_pp_ch = ctx->ipu_pp_mem_ch = ctx->ipu_mem_rot_pp_ch = ctx->ipu_rot_pp_mem_ch = NULL; ctx->ic = NULL; ctx->pp_mem_irq = ctx->rot_pp_mem_irq = -1; }
static int m2mx6_get_ipu_resources(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev; int ret;
ctx->ic = ipu_ic_get(dev->ipu, IC_TASK_POST_PROCESSOR); if (IS_ERR(ctx->ic)) { v4l2_err(&dev->v4l2_dev, "could not get IC PP\n"); ret = PTR_ERR(ctx->ic); goto err; }
/* get our IDMAC channels */ ctx->ipu_mem_pp_ch = ipu_idmac_get(dev->ipu, IPUV3_CHANNEL_MEM_IC_PP); ctx->ipu_pp_mem_ch = ipu_idmac_get(dev->ipu, IPUV3_CHANNEL_IC_PP_MEM); ctx->ipu_mem_rot_pp_ch = ipu_idmac_get(dev->ipu, IPUV3_CHANNEL_MEM_ROT_PP); ctx->ipu_rot_pp_mem_ch = ipu_idmac_get(dev->ipu, IPUV3_CHANNEL_ROT_PP_MEM); if (IS_ERR(ctx->ipu_mem_pp_ch) || IS_ERR(ctx->ipu_pp_mem_ch) || IS_ERR(ctx->ipu_mem_rot_pp_ch) || IS_ERR(ctx->ipu_rot_pp_mem_ch)) { v4l2_err(&dev->v4l2_dev, "could not acquire IDMAC channels\n"); ret = -EBUSY; goto err; }
/* acquire the EOF interrupts */ ctx->pp_mem_irq = ipu_idmac_channel_irq(dev->ipu, ctx->ipu_pp_mem_ch, IPU_IRQ_EOF); ctx->rot_pp_mem_irq = ipu_idmac_channel_irq(dev->ipu, ctx->ipu_rot_pp_mem_ch, IPU_IRQ_EOF);
ret = devm_request_irq(dev->v4l2_dev.dev, ctx->pp_mem_irq, m2mx6_norotate_irq, 0, MEM2MEM_NAME, dev); if (ret < 0) { v4l2_err(&dev->v4l2_dev, "could not acquire irq %d\n", ctx->pp_mem_irq); goto err; }
ret = devm_request_irq(dev->v4l2_dev.dev, ctx->rot_pp_mem_irq, m2mx6_rotate_irq, 0, MEM2MEM_NAME, dev); if (ret < 0) { v4l2_err(&dev->v4l2_dev, "could not acquire irq %d\n", ctx->rot_pp_mem_irq); goto err; }
return 0; err: m2mx6_release_ipu_resources(ctx); return ret; }
/* * video ioctls */ static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1); strncpy(cap->card, MEM2MEM_NAME, sizeof(cap->card) - 1); cap->bus_info[0] = 0; cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
return 0; }
static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) { struct m2mx6_pixfmt *fmt; int i, num = 0;
for (i = 0; i < NUM_FORMATS; ++i) { if (m2mx6_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 = &m2mx6_formats[i]; strncpy(f->description, fmt->name, sizeof(f->description) - 1); f->pixelformat = fmt->fourcc; return 0; }
/* Format not found */ return -EINVAL; }
static void free_rot_intermediate_buffer(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev;
if (ctx->rot_intermediate_buf[0]) { dma_free_coherent(dev->v4l2_dev.dev, 2 * ctx->rot_intermediate_buf_size, ctx->rot_intermediate_buf[0], ctx->rot_intermediate_phys[0]); ctx->rot_intermediate_buf[0] = ctx->rot_intermediate_buf[1] = NULL; }
}
static int alloc_rot_intermediate_buffer(struct m2mx6_ctx *ctx) { struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *d_q_data; unsigned long newlen;
d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); newlen = PAGE_ALIGN(d_q_data->sizeimage);
if (ctx->rot_intermediate_buf[0]) { if (ctx->rot_intermediate_buf_size == newlen) goto out; free_rot_intermediate_buffer(ctx); }
ctx->rot_intermediate_buf_size = newlen; ctx->rot_intermediate_buf[0] = (void *)dma_alloc_coherent(dev->v4l2_dev.dev, 2 * ctx->rot_intermediate_buf_size, &ctx->rot_intermediate_phys[0], GFP_DMA | GFP_KERNEL); if (!ctx->rot_intermediate_buf[0]) { v4l2_err(&dev->v4l2_dev, "failed to alloc rotation intermediate buffer\n"); return -ENOMEM; }
ctx->rot_intermediate_buf[1] = ctx->rot_intermediate_buf[0] + ctx->rot_intermediate_buf_size; ctx->rot_intermediate_phys[1] = ctx->rot_intermediate_phys[0] + ctx->rot_intermediate_buf_size; out: return 0; }
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 m2mx6_g_fmt(struct m2mx6_ctx *ctx, struct v4l2_format *f) { struct vb2_queue *vq; struct m2mx6_q_data *q_data;
vq = v4l2_m2m_get_vq(ctx->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->width * q_data->fmt->depth) >> 3; 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 m2mx6_g_fmt(priv, f); }
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { return m2mx6_g_fmt(priv, f); }
static int m2mx6_try_fmt(struct m2mx6_ctx *ctx, struct v4l2_format *f, struct m2mx6_pixfmt *fmt) { unsigned int num_rows, num_cols; enum v4l2_field field; u32 w_align, h_align;
field = f->fmt.pix.field;
if (field == V4L2_FIELD_ANY) field = V4L2_FIELD_NONE; else if (V4L2_FIELD_NONE != field) return -EINVAL;
if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { if (!ctx->num_rows || !ctx->num_cols) { v4l2_err(&ctx->dev->v4l2_dev, "call capture S_FMT first to determine segmentation\n"); return -EAGAIN; }
num_rows = ctx->num_rows; num_cols = ctx->num_cols; } else { num_rows = m2mx6_num_stripes(f->fmt.pix.height); num_cols = m2mx6_num_stripes(f->fmt.pix.width); }
/* V4L2 specification suggests the driver corrects the format struct * if any of the dimensions is unsupported */
f->fmt.pix.field = field;
/* * We have to adjust the width such that the segment physaddrs and * U and V plane offsets are multiples of 8 bytes as required by * the IPU DMA Controller. For the planar formats, this corresponds * to a pixel alignment of 16 times num_cols (but use a more formal * equation since the variables are available). For all the packed * formats, 8 times num_cols is good enough. * * For height alignment, we just have to ensure that the segment * heights are even whole numbers, so h_align = 2 * num_rows. */ if (fmt->y_depth) w_align = (64 * fmt->uv_width_dec * num_cols) / fmt->y_depth; else w_align = 8 * num_cols;
w_align = ilog2(w_align); h_align = ilog2(2 * num_rows);
v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, w_align, &f->fmt.pix.height, MIN_H, MAX_H, h_align, S_ALIGN);
f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; 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 m2mx6_pixfmt *fmt; struct m2mx6_ctx *ctx = priv;
fmt = m2mx6_get_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 m2mx6_try_fmt(ctx, f, fmt); }
static int vidioc_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { struct m2mx6_pixfmt *fmt; struct m2mx6_ctx *ctx = priv;
fmt = m2mx6_get_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 m2mx6_try_fmt(ctx, f, fmt); }
static int m2mx6_s_fmt(struct m2mx6_ctx *ctx, struct v4l2_format *f) { struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *q_data; struct vb2_queue *vq;
vq = v4l2_m2m_get_vq(ctx->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(&dev->v4l2_dev, "%s queue busy\n", __func__); return -EBUSY; }
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { /* set segmentation */ ctx->num_rows = m2mx6_num_stripes(f->fmt.pix.height); ctx->num_cols = m2mx6_num_stripes(f->fmt.pix.width); ctx->num_segs = ctx->num_cols * ctx->num_rows; }
q_data->fmt = m2mx6_get_format(f); q_data->width = f->fmt.pix.width; q_data->height = f->fmt.pix.height; q_data->bytesperline = f->fmt.pix.bytesperline; if (q_data->fmt->y_depth) { q_data->stride = (q_data->fmt->y_depth * q_data->width) >> 3; q_data->rot_stride = (q_data->fmt->y_depth * q_data->height) >> 3; } else { q_data->stride = q_data->bytesperline; q_data->rot_stride = (q_data->fmt->depth * q_data->height) >> 3; }
q_data->sizeimage = q_data->bytesperline * q_data->height;
q_data->seg_height = q_data->height / ctx->num_rows; q_data->seg_width = q_data->width / ctx->num_cols;
m2mx6_calc_seg_offsets(ctx, f->type);
v4l2_info(&dev->v4l2_dev, "%s format: %dx%d (%d %dx%d segments), %c%c%c%c\n", f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ? "Capture" : "Output", q_data->width, q_data->height, ctx->num_segs, q_data->seg_width, q_data->seg_height, q_data->fmt->fourcc & 0xff, (q_data->fmt->fourcc >> 8) & 0xff, (q_data->fmt->fourcc >> 16) & 0xff, (q_data->fmt->fourcc >> 24) & 0xff);
return 0; }
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct m2mx6_ctx *ctx = priv; int ret;
ret = vidioc_try_fmt_vid_cap(file, ctx, f); if (ret) return ret;
ret = m2mx6_s_fmt(ctx, f); if (ret) return ret;
return 0; }
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 m2mx6_s_fmt(priv, f); }
static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *reqbufs) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); }
static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); }
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); }
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); }
static int vidioc_expbuf(struct file *file, void *priv, struct v4l2_exportbuffer *eb) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); }
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); }
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) { struct m2mx6_ctx *ctx = priv;
return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); }
static int vidioc_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *qc) { struct v4l2_queryctrl *c;
c = m2mx6_get_ctrl(qc->id); if (!c) return -EINVAL;
*qc = *c; return 0; }
static int vidioc_g_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) { struct m2mx6_ctx *ctx = priv; int ret = 0;
switch (ctrl->id) { case V4L2_CID_HFLIP: ctrl->value = ctx->hflip ? 1 : 0; break; case V4L2_CID_VFLIP: ctrl->value = ctx->vflip ? 1 : 0; break; case V4L2_CID_ROTATE: ctrl->value = ctx->rotation; break; default: v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n"); ret = -EINVAL; break; }
return ret; }
static int check_ctrl_val(struct m2mx6_ctx *ctx, struct v4l2_control *ctrl) { struct v4l2_queryctrl *c;
c = m2mx6_get_ctrl(ctrl->id); if (!c) return -EINVAL;
if (ctrl->value < c->minimum || ctrl->value > c->maximum) { v4l2_err(&ctx->dev->v4l2_dev, "Value out of range\n"); return -ERANGE; }
return 0; }
static int vidioc_s_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) { struct m2mx6_ctx *ctx = priv; struct m2mx6_dev *dev = ctx->dev; struct vb2_queue *vq; enum ipu_rotate_mode rot_mode; bool hflip, vflip; int rotation; int ret = 0;
vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); if (!vq) return -EINVAL;
/* can't change rotation mid-streaming */ if (vb2_is_streaming(vq)) { v4l2_err(&dev->v4l2_dev, "%s: not allowed while streaming\n", __func__); return -EBUSY; }
ret = check_ctrl_val(ctx, ctrl); if (ret) return ret;
rotation = ctx->rotation; hflip = ctx->hflip; vflip = ctx->vflip;
switch (ctrl->id) { case V4L2_CID_HFLIP: hflip = (ctrl->value == 1); break; case V4L2_CID_VFLIP: vflip = (ctrl->value == 1); break; case V4L2_CID_ROTATE: rotation = ctrl->value; break; default: v4l2_err(&dev->v4l2_dev, "Invalid control\n"); return -EINVAL; }
ret = ipu_degrees_to_rot_mode(&rot_mode, rotation, hflip, vflip); if (ret) return ret;
ctx->rotation = rotation; ctx->hflip = hflip; ctx->vflip = vflip; ctx->rot_mode = rot_mode;
return 0; }
static const struct v4l2_ioctl_ops m2mx6_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 = vidioc_reqbufs, .vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf, .vidioc_dqbuf = vidioc_dqbuf, .vidioc_expbuf = vidioc_expbuf,
.vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff,
.vidioc_queryctrl = vidioc_queryctrl, .vidioc_g_ctrl = vidioc_g_ctrl, .vidioc_s_ctrl = vidioc_s_ctrl, };
/* * Queue operations */
static int m2mx6_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], void *alloc_ctxs[]) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(vq); struct m2mx6_q_data *q_data; unsigned int size, count = *nbuffers;
q_data = get_q_data(ctx, vq->type);
size = q_data->width * q_data->height * q_data->fmt->depth >> 3;
while (size * count > MEM2MEM_VID_MEM_LIMIT) count--;
*nplanes = 1; *nbuffers = count; sizes[0] = size;
alloc_ctxs[0] = ctx->dev->alloc_ctx;
return 0; }
static int m2mx6_buf_prepare(struct vb2_buffer *vb) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct m2mx6_dev *dev = ctx->dev; struct m2mx6_q_data *q_data;
q_data = get_q_data(ctx, vb->vb2_queue->type);
if (vb2_plane_size(vb, 0) < q_data->sizeimage) { v4l2_err(&dev->v4l2_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 m2mx6_buf_queue(struct vb2_buffer *vb) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); }
static void m2mx6_wait_prepare(struct vb2_queue *q) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(q); m2mx6_unlock(ctx); }
static void m2mx6_wait_finish(struct vb2_queue *q) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(q); m2mx6_lock(ctx); }
static int m2mx6_start_streaming(struct vb2_queue *q, unsigned int count) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(q); int ret;
if (!ctx->ipu_mem_pp_ch) { ret = m2mx6_get_ipu_resources(ctx); if (ret) return ret; }
if (ctx->rot_mode >= IPU_ROTATE_90_RIGHT && !ctx->rot_intermediate_buf[0]) { ret = alloc_rot_intermediate_buffer(ctx); if (ret) goto relres; }
return 0; relres: m2mx6_release_ipu_resources(ctx); return ret; }
static int m2mx6_stop_streaming(struct vb2_queue *q) { struct m2mx6_ctx *ctx = vb2_get_drv_priv(q);
m2mx6_release_ipu_resources(ctx); free_rot_intermediate_buffer(ctx);
return 0; }
static struct vb2_ops m2mx6_qops = { .queue_setup = m2mx6_queue_setup, .buf_prepare = m2mx6_buf_prepare, .buf_queue = m2mx6_buf_queue, .wait_prepare = m2mx6_wait_prepare, .wait_finish = m2mx6_wait_finish, .start_streaming = m2mx6_start_streaming, .stop_streaming = m2mx6_stop_streaming, };
static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { struct m2mx6_ctx *ctx = priv; int ret;
memset(src_vq, 0, sizeof(*src_vq)); 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 = &m2mx6_qops; src_vq->mem_ops = &vb2_dma_contig_memops; src_vq->timestamp_type = 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; 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 = &m2mx6_qops; dst_vq->mem_ops = &vb2_dma_contig_memops; dst_vq->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY;
return vb2_queue_init(dst_vq); }
/* * File operations */ static int m2mx6_open(struct file *file) { struct m2mx6_dev *dev = video_drvdata(file); struct m2mx6_ctx *ctx; int ret = 0;
if (mutex_lock_interruptible(&dev->dev_mutex)) return -ERESTARTSYS;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) { ret = -ENOMEM; goto unlock; }
file->private_data = ctx; ctx->dev = dev;
ctx->q_data[V4L2_M2M_SRC].fmt = &m2mx6_formats[0]; ctx->q_data[V4L2_M2M_DST].fmt = &m2mx6_formats[0];
ctx->pp_mem_irq = ctx->rot_pp_mem_irq = -1;
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); kfree(ctx); goto unlock; }
unlock: mutex_unlock(&dev->dev_mutex); return ret;
}
static int m2mx6_release(struct file *file) { struct m2mx6_dev *dev = video_drvdata(file); struct m2mx6_ctx *ctx = file->private_data;
mutex_lock(&dev->dev_mutex);
/* * in case appplication did not call streamoff, * release IPU resources here */ m2mx6_release_ipu_resources(ctx);
free_rot_intermediate_buffer(ctx);
v4l2_m2m_ctx_release(ctx->m2m_ctx); kfree(ctx);
mutex_unlock(&dev->dev_mutex); return 0; }
static unsigned int m2mx6_poll(struct file *file, struct poll_table_struct *wait) { struct m2mx6_dev *dev = video_drvdata(file); struct m2mx6_ctx *ctx = file->private_data; int ret;
if (mutex_lock_interruptible(&dev->dev_mutex)) return -ERESTARTSYS;
ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait);
mutex_unlock(&dev->dev_mutex); return ret; }
static int m2mx6_mmap(struct file *file, struct vm_area_struct *vma) { struct m2mx6_dev *dev = video_drvdata(file); struct m2mx6_ctx *ctx = file->private_data; 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 m2mx6_fops = { .owner = THIS_MODULE, .open = m2mx6_open, .release = m2mx6_release, .poll = m2mx6_poll, .unlocked_ioctl = video_ioctl2, .mmap = m2mx6_mmap, };
static struct video_device m2mx6_videodev = { .name = MEM2MEM_NAME, .fops = &m2mx6_fops, .ioctl_ops = &m2mx6_ioctl_ops, .minor = -1, .release = video_device_release, .vfl_dir = VFL_DIR_M2M, };
static struct v4l2_m2m_ops m2m_ops = { .device_run = m2mx6_device_run, .job_abort = m2mx6_job_abort, .lock = m2mx6_lock, .unlock = m2mx6_unlock, };
static int of_dev_node_match(struct device *dev, void *data) { return dev->of_node == data; }
static struct ipu_soc *m2mx6_find_ipu(struct m2mx6_dev *dev, struct device_node *node) { struct device_node *ipu_node; struct device *ipu_dev;
ipu_node = of_parse_phandle(node, "ipu", 0); if (!ipu_node) { v4l2_err(&dev->v4l2_dev, "missing ipu phandle!\n"); return NULL; }
ipu_dev = bus_find_device(&platform_bus_type, NULL, ipu_node, of_dev_node_match); of_node_put(ipu_node);
if (!ipu_dev) { v4l2_err(&dev->v4l2_dev, "failed to find ipu device!\n"); return NULL; }
return dev_get_drvdata(ipu_dev); }
static int m2mx6_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct m2mx6_dev *dev; struct video_device *vfd; int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM;
spin_lock_init(&dev->irqlock);
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); if (ret) return ret;
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
/* get our IPU */ dev->ipu = m2mx6_find_ipu(dev, node); if (IS_ERR_OR_NULL(dev->ipu)) { v4l2_err(&dev->v4l2_dev, "could not get ipu\n"); ret = -ENODEV; goto unreg_dev; }
mutex_init(&dev->dev_mutex);
vfd = video_device_alloc(); if (!vfd) { v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); ret = -ENOMEM; goto unreg_dev; }
*vfd = m2mx6_videodev; vfd->v4l2_dev = &dev->v4l2_dev; vfd->lock = &dev->dev_mutex;
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); video_device_release(vfd); goto unreg_dev; }
ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); if (ret) { v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); video_device_release(vfd); goto rel_m2m; }
video_set_drvdata(vfd, dev); snprintf(vfd->name, sizeof(vfd->name), "%s", m2mx6_videodev.name); dev->vfd = vfd; v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d, on ipu%d\n", vfd->num, ipu_get_num(dev->ipu));
platform_set_drvdata(pdev, dev);
dev->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); if (IS_ERR(dev->alloc_ctx)) { v4l2_err(&dev->v4l2_dev, "Failed to alloc vb2 context\n"); ret = PTR_ERR(dev->alloc_ctx); goto unreg_vdev; }
return 0;
unreg_vdev: video_unregister_device(dev->vfd); rel_m2m: v4l2_m2m_release(dev->m2m_dev); unreg_dev: v4l2_device_unregister(&dev->v4l2_dev); return ret; }
static int m2mx6_remove(struct platform_device *pdev) { struct m2mx6_dev *dev = (struct m2mx6_dev *)platform_get_drvdata(pdev);
v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME "\n"); v4l2_m2m_release(dev->m2m_dev); vb2_dma_contig_cleanup_ctx(dev->alloc_ctx); video_unregister_device(dev->vfd);
v4l2_device_unregister(&dev->v4l2_dev);
return 0; }
static struct of_device_id m2mx6_dt_ids[] = { { .compatible = "fsl,imx6-v4l2-mem2mem" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, m2mx6_dt_ids);
static struct platform_driver m2mx6_pdrv = { .probe = m2mx6_probe, .remove = m2mx6_remove, .driver = { .name = MEM2MEM_NAME, .owner = THIS_MODULE, .of_match_table = m2mx6_dt_ids, }, };
module_platform_driver(m2mx6_pdrv);
| |