blob: 17f3c07fa8a42e4f2413a0bbcd12af2899995291 [file] [log] [blame]
/*
* Copyright (C) 2010-2015 Freescale Semiconductor, Inc.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* Based on STMP378X PxP driver
* Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
*/
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/vmalloc.h>
#include <linux/videodev2.h>
#include <linux/dmaengine.h>
#include <linux/pxp_dma.h>
#include <linux/delay.h>
#include <linux/console.h>
#include <linux/mxcfb.h>
#include <linux/platform_data/dma-imx.h>
#include <media/videobuf-dma-contig.h>
#include <media/v4l2-common.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "mxc_pxp_v4l2.h"
#define PXP_DRIVER_NAME "pxp-v4l2"
#define PXP_DRIVER_MAJOR 2
#define PXP_DRIVER_MINOR 0
#define PXP_DEF_BUFS 2
#define PXP_MIN_PIX 8
#define V4L2_OUTPUT_TYPE_INTERNAL 4
static int video_nr = -1; /* -1 ==> auto assign */
static struct pxp_data_format pxp_s0_formats[] = {
{
.name = "24-bit RGB",
.bpp = 4,
.fourcc = V4L2_PIX_FMT_RGB24,
.colorspace = V4L2_COLORSPACE_SRGB,
}, {
.name = "16-bit RGB 5:6:5",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_RGB565,
.colorspace = V4L2_COLORSPACE_SRGB,
}, {
.name = "16-bit RGB 5:5:5",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_RGB555,
.colorspace = V4L2_COLORSPACE_SRGB,
}, {
.name = "YUV 4:2:0 Planar",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_YUV420,
.colorspace = V4L2_COLORSPACE_JPEG,
}, {
.name = "YUV 4:2:2 Planar",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_YUV422P,
.colorspace = V4L2_COLORSPACE_JPEG,
}, {
.name = "UYVY",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_UYVY,
.colorspace = V4L2_COLORSPACE_JPEG,
}, {
.name = "YUYV",
.bpp = 2,
.fourcc = V4L2_PIX_FMT_YUYV,
.colorspace = V4L2_COLORSPACE_JPEG,
}, {
.name = "YUV32",
.bpp = 4,
.fourcc = V4L2_PIX_FMT_YUV32,
.colorspace = V4L2_COLORSPACE_JPEG,
},
};
static unsigned int v4l2_fmt_to_pxp_fmt(u32 v4l2_pix_fmt)
{
u32 pxp_fmt = 0;
if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB24)
pxp_fmt = PXP_PIX_FMT_RGB24;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB565)
pxp_fmt = PXP_PIX_FMT_RGB565;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_RGB555)
pxp_fmt = PXP_PIX_FMT_RGB555;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV420)
pxp_fmt = PXP_PIX_FMT_YUV420P;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV422P)
pxp_fmt = PXP_PIX_FMT_YUV422P;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_UYVY)
pxp_fmt = PXP_PIX_FMT_UYVY;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUV32)
pxp_fmt = PXP_PIX_FMT_VUY444;
else if (v4l2_pix_fmt == V4L2_PIX_FMT_YUYV)
pxp_fmt = PXP_PIX_FMT_YUYV;
return pxp_fmt;
}
struct v4l2_queryctrl pxp_controls[] = {
{
.id = V4L2_CID_HFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Horizontal Flip",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0,
.flags = 0,
}, {
.id = V4L2_CID_VFLIP,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Vertical Flip",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0,
.flags = 0,
}, {
.id = V4L2_CID_PRIVATE_BASE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Rotation",
.minimum = 0,
.maximum = 270,
.step = 90,
.default_value = 0,
.flags = 0,
}, {
.id = V4L2_CID_PRIVATE_BASE + 1,
.name = "Background Color",
.minimum = 0,
.maximum = 0xFFFFFF,
.step = 1,
.default_value = 0,
.flags = 0,
.type = V4L2_CTRL_TYPE_INTEGER,
}, {
.id = V4L2_CID_PRIVATE_BASE + 2,
.name = "Set S0 Chromakey",
.minimum = -1,
.maximum = 0xFFFFFF,
.step = 1,
.default_value = -1,
.flags = 0,
.type = V4L2_CTRL_TYPE_INTEGER,
}, {
.id = V4L2_CID_PRIVATE_BASE + 3,
.name = "YUV Colorspace",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 0,
.flags = 0,
.type = V4L2_CTRL_TYPE_BOOLEAN,
},
};
static void free_dma_buf(struct pxps *pxp, struct dma_mem *buf)
{
dma_free_coherent(&pxp->pdev->dev, buf->size, buf->vaddr, buf->paddr);
dev_dbg(&pxp->pdev->dev,
"free dma size:0x%x, paddr:0x%x\n",
buf->size, buf->paddr);
memset(buf, 0, sizeof(*buf));
}
static int alloc_dma_buf(struct pxps *pxp, struct dma_mem *buf)
{
buf->vaddr = dma_alloc_coherent(&pxp->pdev->dev, buf->size, &buf->paddr,
GFP_DMA | GFP_KERNEL);
if (!buf->vaddr) {
dev_err(&pxp->pdev->dev,
"cannot get dma buf size:0x%x\n", buf->size);
return -ENOMEM;
}
dev_dbg(&pxp->pdev->dev,
"alloc dma buf size:0x%x, paddr:0x%x\n", buf->size, buf->paddr);
return 0;
}
/* callback function */
static void video_dma_done(void *arg)
{
struct pxp_tx_desc *tx_desc = to_tx_desc(arg);
struct dma_chan *chan = tx_desc->txd.chan;
struct pxp_channel *pxp_chan = to_pxp_channel(chan);
struct pxps *pxp = pxp_chan->client;
struct videobuf_buffer *vb;
dev_dbg(chan->device->dev, "callback cookie %d, active DMA 0x%08x\n",
tx_desc->txd.cookie,
pxp->active ? sg_dma_address(&pxp->active->sg[0]) : 0);
spin_lock(&pxp->lock);
if (pxp->active) {
vb = &pxp->active->vb;
list_del_init(&vb->queue);
vb->state = VIDEOBUF_DONE;
do_gettimeofday(&vb->ts);
vb->field_count++;
wake_up(&vb->done);
}
if (list_empty(&pxp->outq)) {
pxp->active = NULL;
spin_unlock(&pxp->lock);
return;
}
pxp->active = list_entry(pxp->outq.next,
struct pxp_buffer, vb.queue);
pxp->active->vb.state = VIDEOBUF_ACTIVE;
spin_unlock(&pxp->lock);
}
static bool chan_filter(struct dma_chan *chan, void *arg)
{
if (imx_dma_is_pxp(chan))
return true;
else
return false;
}
static int acquire_dma_channel(struct pxps *pxp)
{
dma_cap_mask_t mask;
struct dma_chan *chan;
struct pxp_channel **pchan = &pxp->pxp_channel[0];
if (*pchan) {
struct videobuf_buffer *vb, *_vb;
dma_release_channel(&(*pchan)->dma_chan);
*pchan = NULL;
pxp->active = NULL;
list_for_each_entry_safe(vb, _vb, &pxp->outq, queue) {
list_del_init(&vb->queue);
vb->state = VIDEOBUF_ERROR;
wake_up(&vb->done);
}
}
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_PRIVATE, mask);
chan = dma_request_channel(mask, chan_filter, NULL);
if (!chan)
return -EBUSY;
*pchan = to_pxp_channel(chan);
(*pchan)->client = pxp;
return 0;
}
static int _get_fbinfo(struct fb_info **fbi)
{
int i;
for (i = 0; i < num_registered_fb; i++) {
char *idstr = registered_fb[i]->fix.id;
if (strncmp(idstr, "mxs", 3) == 0) {
*fbi = registered_fb[i];
return 0;
}
}
return -ENODEV;
}
static int pxp_set_fbinfo(struct pxps *pxp)
{
struct v4l2_framebuffer *fb = &pxp->fb;
int err;
err = _get_fbinfo(&pxp->fbi);
if (err)
return err;
fb->fmt.width = pxp->fbi->var.xres;
fb->fmt.height = pxp->fbi->var.yres;
pxp->pxp_conf.out_param.stride = pxp->fbi->var.xres;
if (pxp->fbi->var.bits_per_pixel == 16)
fb->fmt.pixelformat = V4L2_PIX_FMT_RGB565;
else
fb->fmt.pixelformat = V4L2_PIX_FMT_RGB24;
fb->base = (void *)pxp->fbi->fix.smem_start;
return 0;
}
static int _get_cur_fb_blank(struct pxps *pxp)
{
struct fb_info *fbi;
mm_segment_t old_fs;
int err = 0;
err = _get_fbinfo(&fbi);
if (err)
return err;
if (fbi->fbops->fb_ioctl) {
old_fs = get_fs();
set_fs(KERNEL_DS);
err = fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_BLANK,
(unsigned int)(&pxp->fb_blank));
set_fs(old_fs);
}
return err;
}
static int pxp_show_buf(struct pxps *pxp, unsigned long paddr)
{
struct fb_info *fbi = pxp->fbi;
int ret = -EINVAL;
if (paddr == 0) {
dev_err(&pxp->pdev->dev, "Invalid paddr\n");
return ret;
}
console_lock();
fbi->fix.smem_start = paddr;
ret = fb_pan_display(fbi, &fbi->var);
console_unlock();
return ret;
}
static int set_fb_blank(int blank)
{
struct fb_info *fbi;
int err = 0;
err = _get_fbinfo(&fbi);
if (err)
return err;
console_lock();
fb_blank(fbi, blank);
console_unlock();
return err;
}
static int pxp_set_cstate(struct pxps *pxp, struct v4l2_control *vc)
{
if (vc->id == V4L2_CID_HFLIP) {
pxp->pxp_conf.proc_data.hflip = vc->value;
} else if (vc->id == V4L2_CID_VFLIP) {
pxp->pxp_conf.proc_data.vflip = vc->value;
} else if (vc->id == V4L2_CID_PRIVATE_BASE) {
if (vc->value % 90)
return -ERANGE;
pxp->pxp_conf.proc_data.rotate = vc->value;
} else if (vc->id == V4L2_CID_PRIVATE_BASE + 1) {
pxp->pxp_conf.proc_data.bgcolor = vc->value;
} else if (vc->id == V4L2_CID_PRIVATE_BASE + 2) {
pxp->pxp_conf.s0_param.color_key = vc->value;
} else if (vc->id == V4L2_CID_PRIVATE_BASE + 3) {
pxp->pxp_conf.proc_data.yuv = vc->value;
}
return 0;
}
static int pxp_get_cstate(struct pxps *pxp, struct v4l2_control *vc)
{
if (vc->id == V4L2_CID_HFLIP)
vc->value = pxp->pxp_conf.proc_data.hflip;
else if (vc->id == V4L2_CID_VFLIP)
vc->value = pxp->pxp_conf.proc_data.vflip;
else if (vc->id == V4L2_CID_PRIVATE_BASE)
vc->value = pxp->pxp_conf.proc_data.rotate;
else if (vc->id == V4L2_CID_PRIVATE_BASE + 1)
vc->value = pxp->pxp_conf.proc_data.bgcolor;
else if (vc->id == V4L2_CID_PRIVATE_BASE + 2)
vc->value = pxp->pxp_conf.s0_param.color_key;
else if (vc->id == V4L2_CID_PRIVATE_BASE + 3)
vc->value = pxp->pxp_conf.proc_data.yuv;
return 0;
}
static int pxp_enumoutput(struct file *file, void *fh,
struct v4l2_output *o)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
if (o->index > 1)
return -EINVAL;
memset(o, 0, sizeof(struct v4l2_output));
if (o->index == 0) {
strcpy(o->name, "PxP Display Output");
pxp->output = 0;
} else {
strcpy(o->name, "PxP Virtual Output");
pxp->output = 1;
}
o->type = V4L2_OUTPUT_TYPE_INTERNAL;
o->std = 0;
o->reserved[0] = pxp->outbuf.paddr;
return 0;
}
static int pxp_g_output(struct file *file, void *fh,
unsigned int *i)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
*i = pxp->output;
return 0;
}
static int pxp_s_output(struct file *file, void *fh,
unsigned int i)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct v4l2_pix_format *fmt = (struct v4l2_pix_format*)(&pxp->fb.fmt);
u32 size;
int ret, bpp;
if (i > 1)
return -EINVAL;
/* Output buffer is same format as fbdev */
if (fmt->pixelformat == V4L2_PIX_FMT_RGB24 ||
fmt->pixelformat == V4L2_PIX_FMT_YUV32)
bpp = 4;
else
bpp = 2;
size = fmt->width * fmt->height * bpp;
if (size > pxp->outbuf.size) {
if (pxp->outbuf.vaddr)
free_dma_buf(pxp, &pxp->outbuf);
pxp->outbuf.size = size;
ret = alloc_dma_buf(pxp, &pxp->outbuf);
if (ret < 0)
return ret;
}
memset(pxp->outbuf.vaddr, 0x0, pxp->outbuf.size);
pxp->pxp_conf.out_param.width = fmt->width;
pxp->pxp_conf.out_param.height = fmt->height;
if (fmt->pixelformat == V4L2_PIX_FMT_RGB24)
pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB24;
else
pxp->pxp_conf.out_param.pixel_fmt = PXP_PIX_FMT_RGB565;
return 0;
}
static int pxp_enum_fmt_video_output(struct file *file, void *fh,
struct v4l2_fmtdesc *fmt)
{
enum v4l2_buf_type type = fmt->type;
unsigned int index = fmt->index;
if (fmt->index >= ARRAY_SIZE(pxp_s0_formats))
return -EINVAL;
memset(fmt, 0, sizeof(struct v4l2_fmtdesc));
fmt->index = index;
fmt->type = type;
fmt->pixelformat = pxp_s0_formats[index].fourcc;
strcpy(fmt->description, pxp_s0_formats[index].name);
return 0;
}
static int pxp_g_fmt_video_output(struct file *file, void *fh,
struct v4l2_format *f)
{
struct v4l2_pix_format *pf = &f->fmt.pix;
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct pxp_data_format *fmt = pxp->s0_fmt;
pf->width = pxp->pxp_conf.s0_param.width;
pf->height = pxp->pxp_conf.s0_param.height;
pf->pixelformat = fmt->fourcc;
pf->field = V4L2_FIELD_NONE;
pf->bytesperline = fmt->bpp * pf->width;
pf->sizeimage = pf->bytesperline * pf->height;
pf->colorspace = fmt->colorspace;
pf->priv = 0;
return 0;
}
static struct pxp_data_format *pxp_get_format(struct v4l2_format *f)
{
struct pxp_data_format *fmt;
int i;
for (i = 0; i < ARRAY_SIZE(pxp_s0_formats); i++) {
fmt = &pxp_s0_formats[i];
if (fmt->fourcc == f->fmt.pix.pixelformat)
break;
}
if (i == ARRAY_SIZE(pxp_s0_formats))
return NULL;
return &pxp_s0_formats[i];
}
static int pxp_try_fmt_video_output(struct file *file, void *fh,
struct v4l2_format *f)
{
int w = f->fmt.pix.width;
int h = f->fmt.pix.height;
struct pxp_data_format *fmt = pxp_get_format(f);
if (!fmt)
return -EINVAL;
w = min(w, 2040);
w = max(w, 8);
h = min(h, 2040);
h = max(h, 8);
f->fmt.pix.field = V4L2_FIELD_NONE;
f->fmt.pix.width = w;
f->fmt.pix.height = h;
f->fmt.pix.pixelformat = fmt->fourcc;
return 0;
}
static int pxp_s_fmt_video_output(struct file *file, void *fh,
struct v4l2_format *f)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct v4l2_pix_format *pf = &f->fmt.pix;
int ret;
ret = acquire_dma_channel(pxp);
if (ret < 0)
return ret;
ret = pxp_try_fmt_video_output(file, fh, f);
if (ret == 0) {
pxp->s0_fmt = pxp_get_format(f);
pxp->pxp_conf.s0_param.pixel_fmt =
v4l2_fmt_to_pxp_fmt(pxp->s0_fmt->fourcc);
pxp->pxp_conf.s0_param.width = pf->width;
pxp->pxp_conf.s0_param.height = pf->height;
}
return ret;
}
static int pxp_g_fmt_output_overlay(struct file *file, void *fh,
struct v4l2_format *f)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct v4l2_window *wf = &f->fmt.win;
memset(wf, 0, sizeof(struct v4l2_window));
wf->chromakey = pxp->s1_chromakey;
wf->global_alpha = pxp->global_alpha;
wf->field = V4L2_FIELD_NONE;
wf->clips = NULL;
wf->clipcount = 0;
wf->bitmap = NULL;
wf->w.left = pxp->pxp_conf.proc_data.srect.left;
wf->w.top = pxp->pxp_conf.proc_data.srect.top;
wf->w.width = pxp->pxp_conf.proc_data.srect.width;
wf->w.height = pxp->pxp_conf.proc_data.srect.height;
return 0;
}
static int pxp_try_fmt_output_overlay(struct file *file, void *fh,
struct v4l2_format *f)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct v4l2_window *wf = &f->fmt.win;
struct v4l2_rect srect;
u32 s1_chromakey = wf->chromakey;
u8 global_alpha = wf->global_alpha;
memcpy(&srect, &(wf->w), sizeof(struct v4l2_rect));
pxp_g_fmt_output_overlay(file, fh, f);
wf->chromakey = s1_chromakey;
wf->global_alpha = global_alpha;
/* Constrain parameters to the input buffer */
wf->w.left = srect.left;
wf->w.top = srect.top;
wf->w.width = min(srect.width,
((__u32)pxp->pxp_conf.s0_param.width - wf->w.left));
wf->w.height = min(srect.height,
((__u32)pxp->pxp_conf.s0_param.height - wf->w.top));
return 0;
}
static int pxp_s_fmt_output_overlay(struct file *file, void *fh,
struct v4l2_format *f)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
struct v4l2_window *wf = &f->fmt.win;
int ret = pxp_try_fmt_output_overlay(file, fh, f);
if (ret == 0) {
pxp->global_alpha = wf->global_alpha;
pxp->s1_chromakey = wf->chromakey;
pxp->pxp_conf.proc_data.srect.left = wf->w.left;
pxp->pxp_conf.proc_data.srect.top = wf->w.top;
pxp->pxp_conf.proc_data.srect.width = wf->w.width;
pxp->pxp_conf.proc_data.srect.height = wf->w.height;
pxp->pxp_conf.ol_param[0].global_alpha = pxp->global_alpha;
pxp->pxp_conf.ol_param[0].color_key = pxp->s1_chromakey;
pxp->pxp_conf.ol_param[0].color_key_enable =
pxp->s1_chromakey_state;
}
return ret;
}
static int pxp_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *r)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
return videobuf_reqbufs(&pxp->s0_vbq, r);
}
static int pxp_querybuf(struct file *file, void *priv,
struct v4l2_buffer *b)
{
int ret;
struct pxps *pxp = video_get_drvdata(video_devdata(file));
ret = videobuf_querybuf(&pxp->s0_vbq, b);
if (!ret) {
struct videobuf_buffer *vb = pxp->s0_vbq.bufs[b->index];
if (b->flags & V4L2_BUF_FLAG_MAPPED)
b->m.offset = videobuf_to_dma_contig(vb);
}
return ret;
}
static int pxp_qbuf(struct file *file, void *priv,
struct v4l2_buffer *b)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
return videobuf_qbuf(&pxp->s0_vbq, b);
}
static int pxp_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *b)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
return videobuf_dqbuf(&pxp->s0_vbq, b, file->f_flags & O_NONBLOCK);
}
static int pxp_streamon(struct file *file, void *priv,
enum v4l2_buf_type t)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
int ret = 0;
if (t != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
_get_cur_fb_blank(pxp);
set_fb_blank(FB_BLANK_UNBLANK);
ret = videobuf_streamon(&pxp->s0_vbq);
if (!ret && (pxp->output == 0))
pxp_show_buf(pxp, pxp->outbuf.paddr);
return ret;
}
static int pxp_streamoff(struct file *file, void *priv,
enum v4l2_buf_type t)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
int ret = 0;
if ((t != V4L2_BUF_TYPE_VIDEO_OUTPUT))
return -EINVAL;
ret = videobuf_streamoff(&pxp->s0_vbq);
pxp_show_buf(pxp, (unsigned long)pxp->fb.base);
if (pxp->fb_blank)
set_fb_blank(FB_BLANK_POWERDOWN);
return ret;
}
static int pxp_buf_setup(struct videobuf_queue *q,
unsigned int *count, unsigned *size)
{
struct pxps *pxp = q->priv_data;
*size = pxp->pxp_conf.s0_param.width *
pxp->pxp_conf.s0_param.height * pxp->s0_fmt->bpp;
if (0 == *count)
*count = PXP_DEF_BUFS;
return 0;
}
static void pxp_buf_free(struct videobuf_queue *q, struct pxp_buffer *buf)
{
struct videobuf_buffer *vb = &buf->vb;
BUG_ON(in_interrupt());
pr_debug("%s (vb=0x%p) 0x%08lx %d\n", __func__,
vb, vb->baddr, vb->bsize);
/*
* This waits until this buffer is out of danger, i.e., until it is no
* longer in STATE_QUEUED or STATE_ACTIVE
*/
videobuf_waiton(q, vb, 0, 0);
videobuf_dma_contig_free(q, vb);
buf->txd = NULL;
vb->state = VIDEOBUF_NEEDS_INIT;
}
static int pxp_buf_prepare(struct videobuf_queue *q,
struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct pxps *pxp = q->priv_data;
struct pxp_config_data *pxp_conf = &pxp->pxp_conf;
struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
struct pxp_tx_desc *desc;
int ret = 0;
int i, length;
if (!pxp->outbuf.paddr) {
dev_err(&pxp->pdev->dev, "Not allocate memory for "
"PxP Out buffer?\n");
return -ENOMEM;
}
vb->width = pxp->pxp_conf.s0_param.width;
vb->height = pxp->pxp_conf.s0_param.height;
vb->size = vb->width * vb->height * pxp->s0_fmt->bpp;
vb->field = V4L2_FIELD_NONE;
if (vb->state != VIDEOBUF_NEEDS_INIT)
pxp_buf_free(q, buf);
if (vb->state == VIDEOBUF_NEEDS_INIT) {
struct pxp_channel *pchan = pxp->pxp_channel[0];
struct scatterlist *sg = &buf->sg[0];
/* This actually (allocates and) maps buffers */
ret = videobuf_iolock(q, vb, NULL);
if (ret) {
pr_err("fail to call videobuf_iolock, ret = %d\n", ret);
goto fail;
}
/*
* sg[0] for input(S0)
* Sg[1] for output
*/
sg_init_table(sg, 3);
buf->txd = pchan->dma_chan.device->device_prep_slave_sg(
&pchan->dma_chan, sg, 3, DMA_FROM_DEVICE,
DMA_PREP_INTERRUPT, NULL);
if (!buf->txd) {
ret = -EIO;
goto fail;
}
buf->txd->callback_param = buf->txd;
buf->txd->callback = video_dma_done;
desc = to_tx_desc(buf->txd);
length = desc->len;
for (i = 0; i < length; i++) {
if (i == 0) {/* S0 */
memcpy(&desc->proc_data, proc_data,
sizeof(struct pxp_proc_data));
pxp_conf->s0_param.paddr =
videobuf_to_dma_contig(vb);
memcpy(&desc->layer_param.s0_param,
&pxp_conf->s0_param,
sizeof(struct pxp_layer_param));
} else if (i == 1) { /* Output */
/* we should always pass the output
* width and height which is the value
* after been rotated.
*/
pxp_conf->out_param.width =
pxp->fb.fmt.width;
pxp_conf->out_param.height =
pxp->fb.fmt.height;
pxp_conf->out_param.paddr = pxp->outbuf.paddr;
memcpy(&desc->layer_param.out_param,
&pxp_conf->out_param,
sizeof(struct pxp_layer_param));
} else if (pxp_conf->ol_param[0].combine_enable) {
/* Overlay */
pxp_conf->ol_param[0].paddr =
(dma_addr_t)pxp->fb.base;
pxp_conf->ol_param[0].width = pxp->fb.fmt.width;
pxp_conf->ol_param[0].height =
pxp->fb.fmt.height;
pxp_conf->ol_param[0].pixel_fmt =
pxp_conf->out_param.pixel_fmt;
memcpy(&desc->layer_param.ol_param,
&pxp_conf->ol_param[0],
sizeof(struct pxp_layer_param));
}
desc = desc->next;
}
vb->state = VIDEOBUF_PREPARED;
}
return 0;
fail:
pxp_buf_free(q, buf);
return ret;
}
static void pxp_buf_queue(struct videobuf_queue *q,
struct videobuf_buffer *vb)
{
struct pxps *pxp = q->priv_data;
struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
struct dma_async_tx_descriptor *txd = buf->txd;
struct pxp_channel *pchan = pxp->pxp_channel[0];
dma_cookie_t cookie;
BUG_ON(!irqs_disabled());
list_add_tail(&vb->queue, &pxp->outq);
if (!pxp->active) {
pxp->active = buf;
vb->state = VIDEOBUF_ACTIVE;
} else {
vb->state = VIDEOBUF_QUEUED;
}
spin_unlock_irq(&pxp->lock);
cookie = txd->tx_submit(txd);
dev_dbg(&pxp->pdev->dev, "Submitted cookie %d DMA 0x%08x\n",
cookie, sg_dma_address(&buf->sg[0]));
mdelay(5);
/* trigger ePxP */
dma_async_issue_pending(&pchan->dma_chan);
spin_lock_irq(&pxp->lock);
if (cookie >= 0)
return;
/* Submit error */
pr_err("%s: Submit error\n", __func__);
vb->state = VIDEOBUF_PREPARED;
list_del_init(&vb->queue);
if (pxp->active == buf)
pxp->active = NULL;
}
static void pxp_buf_release(struct videobuf_queue *q,
struct videobuf_buffer *vb)
{
struct pxps *pxp = q->priv_data;
struct pxp_buffer *buf = container_of(vb, struct pxp_buffer, vb);
unsigned long flags;
spin_lock_irqsave(&pxp->lock, flags);
if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED) &&
!list_empty(&vb->queue)) {
vb->state = VIDEOBUF_ERROR;
list_del_init(&vb->queue);
if (pxp->active == buf)
pxp->active = NULL;
}
spin_unlock_irqrestore(&pxp->lock, flags);
pxp_buf_free(q, buf);
}
static struct videobuf_queue_ops pxp_vbq_ops = {
.buf_setup = pxp_buf_setup,
.buf_prepare = pxp_buf_prepare,
.buf_queue = pxp_buf_queue,
.buf_release = pxp_buf_release,
};
static int pxp_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
memset(cap, 0, sizeof(*cap));
strcpy(cap->driver, "pxp");
strcpy(cap->card, "pxp");
strlcpy(cap->bus_info, dev_name(&pxp->pdev->dev),
sizeof(cap->bus_info));
cap->version = (PXP_DRIVER_MAJOR << 8) + PXP_DRIVER_MINOR;
cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT |
V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int pxp_g_fbuf(struct file *file, void *priv,
struct v4l2_framebuffer *fb)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
memset(fb, 0, sizeof(*fb));
fb->capability = V4L2_FBUF_CAP_EXTERNOVERLAY |
V4L2_FBUF_CAP_CHROMAKEY |
V4L2_FBUF_CAP_LOCAL_ALPHA |
V4L2_FBUF_CAP_GLOBAL_ALPHA;
if (pxp->global_alpha_state)
fb->flags |= V4L2_FBUF_FLAG_GLOBAL_ALPHA;
if (pxp->s1_chromakey_state)
fb->flags |= V4L2_FBUF_FLAG_CHROMAKEY;
return 0;
}
static int pxp_s_fbuf(struct file *file, void *priv,
const struct v4l2_framebuffer *fb)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
pxp->overlay_state =
(fb->flags & V4L2_FBUF_FLAG_OVERLAY) != 0;
pxp->global_alpha_state =
(fb->flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) != 0;
pxp->s1_chromakey_state =
(fb->flags & V4L2_FBUF_FLAG_CHROMAKEY) != 0;
pxp->pxp_conf.ol_param[0].combine_enable = pxp->overlay_state;
pxp->pxp_conf.ol_param[0].global_alpha_enable = pxp->global_alpha_state;
return 0;
}
static int pxp_g_crop(struct file *file, void *fh,
struct v4l2_crop *c)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY)
return -EINVAL;
c->c.left = pxp->pxp_conf.proc_data.drect.left;
c->c.top = pxp->pxp_conf.proc_data.drect.top;
c->c.width = pxp->pxp_conf.proc_data.drect.width;
c->c.height = pxp->pxp_conf.proc_data.drect.height;
return 0;
}
static int pxp_s_crop(struct file *file, void *fh,
const struct v4l2_crop *c)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
int l = c->c.left;
int t = c->c.top;
int w = c->c.width;
int h = c->c.height;
int fbw = pxp->fb.fmt.width;
int fbh = pxp->fb.fmt.height;
if (c->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY)
return -EINVAL;
/* Constrain parameters to FB limits */
w = min(w, fbw);
w = max(w, PXP_MIN_PIX);
h = min(h, fbh);
h = max(h, PXP_MIN_PIX);
/* Round up values to PxP pixel block */
l = roundup(l, PXP_MIN_PIX);
t = roundup(t, PXP_MIN_PIX);
w = roundup(w, PXP_MIN_PIX);
h = roundup(h, PXP_MIN_PIX);
if ((l + w) > fbw)
l = 0;
if ((t + h) > fbh)
t = 0;
pxp->pxp_conf.proc_data.drect.left = l;
pxp->pxp_conf.proc_data.drect.top = t;
pxp->pxp_conf.proc_data.drect.width = w;
pxp->pxp_conf.proc_data.drect.height = h;
memset(pxp->outbuf.vaddr, 0x0, pxp->outbuf.size);
return 0;
}
static int pxp_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
int i;
for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
if (qc->id && qc->id == pxp_controls[i].id) {
memcpy(qc, &(pxp_controls[i]), sizeof(*qc));
return 0;
}
return -EINVAL;
}
static int pxp_g_ctrl(struct file *file, void *priv,
struct v4l2_control *vc)
{
int i;
struct pxps *pxp = video_get_drvdata(video_devdata(file));
for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
if (vc->id == pxp_controls[i].id)
return pxp_get_cstate(pxp, vc);
return -EINVAL;
}
static int pxp_s_ctrl(struct file *file, void *priv,
struct v4l2_control *vc)
{
int i;
struct pxps *pxp = video_get_drvdata(video_devdata(file));
for (i = 0; i < ARRAY_SIZE(pxp_controls); i++)
if (vc->id == pxp_controls[i].id) {
if (vc->value < pxp_controls[i].minimum ||
vc->value > pxp_controls[i].maximum)
return -ERANGE;
return pxp_set_cstate(pxp, vc);
}
memset(pxp->outbuf.vaddr, 0x0, pxp->outbuf.size);
return -EINVAL;
}
void pxp_release(struct video_device *vfd)
{
struct pxps *pxp = video_get_drvdata(vfd);
spin_lock(&pxp->lock);
video_device_release(vfd);
spin_unlock(&pxp->lock);
}
static int pxp_open(struct file *file)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
int ret = 0;
mutex_lock(&pxp->mutex);
pxp->users++;
if (pxp->users > 1) {
pxp->users--;
ret = -EBUSY;
goto out;
}
out:
mutex_unlock(&pxp->mutex);
if (ret)
return ret;
ret = pxp_set_fbinfo(pxp);
if (ret) {
dev_err(&pxp->pdev->dev, "failed to call pxp_set_fbinfo\n");
return ret;
}
videobuf_queue_dma_contig_init(&pxp->s0_vbq,
&pxp_vbq_ops,
&pxp->pdev->dev,
&pxp->lock,
V4L2_BUF_TYPE_VIDEO_OUTPUT,
V4L2_FIELD_NONE,
sizeof(struct pxp_buffer),
pxp,
NULL);
dev_dbg(&pxp->pdev->dev, "call pxp_open\n");
return 0;
}
static int pxp_close(struct file *file)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
pxp_streamoff(file, NULL, V4L2_BUF_TYPE_VIDEO_OUTPUT);
videobuf_stop(&pxp->s0_vbq);
videobuf_mmap_free(&pxp->s0_vbq);
pxp->active = NULL;
mutex_lock(&pxp->mutex);
pxp->users--;
mutex_unlock(&pxp->mutex);
return 0;
}
static int pxp_mmap(struct file *file, struct vm_area_struct *vma)
{
struct pxps *pxp = video_get_drvdata(video_devdata(file));
int ret;
ret = videobuf_mmap_mapper(&pxp->s0_vbq, vma);
return ret;
}
static const struct v4l2_file_operations pxp_fops = {
.owner = THIS_MODULE,
.open = pxp_open,
.release = pxp_close,
.unlocked_ioctl = video_ioctl2,
.mmap = pxp_mmap,
};
static const struct v4l2_ioctl_ops pxp_ioctl_ops = {
.vidioc_querycap = pxp_querycap,
.vidioc_reqbufs = pxp_reqbufs,
.vidioc_querybuf = pxp_querybuf,
.vidioc_qbuf = pxp_qbuf,
.vidioc_dqbuf = pxp_dqbuf,
.vidioc_streamon = pxp_streamon,
.vidioc_streamoff = pxp_streamoff,
.vidioc_enum_output = pxp_enumoutput,
.vidioc_g_output = pxp_g_output,
.vidioc_s_output = pxp_s_output,
.vidioc_enum_fmt_vid_out = pxp_enum_fmt_video_output,
.vidioc_try_fmt_vid_out = pxp_try_fmt_video_output,
.vidioc_g_fmt_vid_out = pxp_g_fmt_video_output,
.vidioc_s_fmt_vid_out = pxp_s_fmt_video_output,
.vidioc_try_fmt_vid_out_overlay = pxp_try_fmt_output_overlay,
.vidioc_g_fmt_vid_out_overlay = pxp_g_fmt_output_overlay,
.vidioc_s_fmt_vid_out_overlay = pxp_s_fmt_output_overlay,
.vidioc_g_fbuf = pxp_g_fbuf,
.vidioc_s_fbuf = pxp_s_fbuf,
.vidioc_g_crop = pxp_g_crop,
.vidioc_s_crop = pxp_s_crop,
.vidioc_queryctrl = pxp_queryctrl,
.vidioc_g_ctrl = pxp_g_ctrl,
.vidioc_s_ctrl = pxp_s_ctrl,
};
static const struct video_device pxp_template = {
.name = "PxP",
.vfl_type = V4L2_CAP_VIDEO_OUTPUT |
V4L2_CAP_VIDEO_OVERLAY |
V4L2_CAP_STREAMING,
.vfl_dir = VFL_DIR_TX,
.fops = &pxp_fops,
.release = pxp_release,
.minor = -1,
.ioctl_ops = &pxp_ioctl_ops,
};
static const struct of_device_id imx_pxpv4l2_dt_ids[] = {
{ .compatible = "fsl,imx6sl-pxp-v4l2", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_pxpv4l2_dt_ids);
static int pxp_probe(struct platform_device *pdev)
{
struct pxps *pxp;
struct v4l2_device *v4l2_dev;
int err = 0;
pxp = kzalloc(sizeof(*pxp), GFP_KERNEL);
if (!pxp) {
dev_err(&pdev->dev, "failed to allocate control object\n");
err = -ENOMEM;
goto exit;
}
dev_set_drvdata(&pdev->dev, pxp);
v4l2_dev = kzalloc(sizeof(*v4l2_dev), GFP_KERNEL);
if (!v4l2_dev) {
dev_err(&pdev->dev, "failed to allocate v4l2_dev structure\n");
err = -ENOMEM;
goto freeirq;
}
err = v4l2_device_register(&pdev->dev, v4l2_dev);
if (err) {
dev_err(&pdev->dev, "register v4l2 device failed\n");
goto freev4l2;
}
INIT_LIST_HEAD(&pxp->outq);
spin_lock_init(&pxp->lock);
mutex_init(&pxp->mutex);
pxp->pdev = pdev;
pxp->vdev = video_device_alloc();
if (!pxp->vdev) {
dev_err(&pdev->dev, "video_device_alloc() failed\n");
err = -ENOMEM;
goto relv4l2;
}
memcpy(pxp->vdev, &pxp_template, sizeof(pxp_template));
pxp->vdev->v4l2_dev = v4l2_dev;
video_set_drvdata(pxp->vdev, pxp);
err = video_register_device(pxp->vdev, VFL_TYPE_GRABBER, video_nr);
if (err) {
dev_err(&pdev->dev, "failed to register video device\n");
goto freevdev;
}
dev_info(&pdev->dev, "initialized\n");
exit:
return err;
freevdev:
video_device_release(pxp->vdev);
relv4l2:
v4l2_device_unregister(v4l2_dev);
freev4l2:
kfree(v4l2_dev);
freeirq:
kfree(pxp);
return err;
}
static int pxp_remove(struct platform_device *pdev)
{
struct pxps *pxp = platform_get_drvdata(pdev);
struct v4l2_device *v4l2_dev = pxp->vdev->v4l2_dev;
video_unregister_device(pxp->vdev);
video_device_release(pxp->vdev);
v4l2_device_unregister(v4l2_dev);
kfree(v4l2_dev);
free_dma_buf(pxp, &pxp->outbuf);
kfree(pxp);
return 0;
}
static struct platform_driver pxp_driver = {
.driver = {
.name = PXP_DRIVER_NAME,
.of_match_table = of_match_ptr(imx_pxpv4l2_dt_ids),
},
.probe = pxp_probe,
.remove = pxp_remove,
};
module_platform_driver(pxp_driver);
module_param(video_nr, int, 0444);
MODULE_DESCRIPTION("MXC PxP V4L2 driver");
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_LICENSE("GPL");