blob: c89143c2d5469b3b918331ddd021b93a10501215 [file] [log] [blame]
/*
* drivers/amlogic/media/video_processor/ionvideo/ionvideo.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
*/
#define DEBUG
#include <linux/amlogic/media/vout/vout_notify.h>
#include <linux/amlogic/media/vfm/vframe.h>
#include <linux/amlogic/media/vfm/vframe_provider.h>
#include <linux/amlogic/media/vfm/vframe_receiver.h>
#include "ionvideo.h"
#include <media/videobuf2-core.h>
#include <media/videobuf2-v4l2.h>
#include <linux/platform_device.h>
#include <linux/amlogic/major.h>
#define IONVIDEO_MODULE_NAME "ionvideo"
#define IONVIDEO_VERSION "1.0"
#define RECEIVER_NAME "ionvideo"
#define IONVIDEO_DEVICE_NAME "ionvideo"
#define V4L2_CID_USER_AMLOGIC_IONVIDEO_BASE (V4L2_CID_USER_BASE + 0x1100)
#define SCALE_4K_TO_1080P 1
static struct mutex ppmgr2_ge2d_canvas_mutex;
static unsigned int video_nr_base = 13;
module_param(video_nr_base, uint, 0644);
MODULE_PARM_DESC(video_nr_base, "videoX start number, 13 is the base nr");
static int scaling_rate = 100;
static int ionvideo_seek_flag;
#ifdef CONFIG_AMLOGIC_MEDIA_MULTI_DEC
static unsigned int n_devs = 9;
#else
static unsigned int n_devs = 1;
#endif
module_param(n_devs, uint, 0644);
MODULE_PARM_DESC(n_devs, "number of video devices to create");
static unsigned int debug;
module_param(debug, uint, 0644);
MODULE_PARM_DESC(debug, "activates debug info");
static unsigned int vid_limit = 16;
module_param(vid_limit, uint, 0644);
MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes");
static unsigned int freerun_mode = 1;
module_param(freerun_mode, uint, 0664);
MODULE_PARM_DESC(freerun_mode, "av synchronization");
static const struct ionvideo_fmt formats[] = {
{.name = "RGB32 (LE)",
.fourcc = V4L2_PIX_FMT_RGB32, /* argb */
.depth = 32, },
{.name = "RGB565 (LE)",
.fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
.depth = 16, },
{.name = "RGB24 (LE)",
.fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
.depth = 24, },
{.name = "RGB24 (BE)",
.fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
.depth = 24, },
{.name = "12 Y/CbCr 4:2:0",
.fourcc = V4L2_PIX_FMT_NV12,
.depth = 12, },
{.name = "12 Y/CrCb 4:2:0",
.fourcc = V4L2_PIX_FMT_NV21,
.depth = 12, },
{.name = "YUV420P",
.fourcc = V4L2_PIX_FMT_YUV420,
.depth = 12, },
{.name = "YVU420P",
.fourcc = V4L2_PIX_FMT_YVU420,
.depth = 12, }
};
/* supported controls
* static struct v4l2_queryctrl ionvideo_node_qctrl[] =
* {
* {
* .id = V4L2_CID_USER_AMLOGIC_IONVIDEO_BASE,
* .type = V4L2_CTRL_TYPE_INTEGER,
* .name = "freerun_mode",
* .minimum = 0,
* .maximum = 1,
* .step = 1,
* .default_value = 1,
* .flags = V4L2_CTRL_FLAG_SLIDER,
* }
* };
*/
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct ionvideo_dev *dev = video_drvdata(file);
if (ctrl->id == V4L2_CID_USER_AMLOGIC_IONVIDEO_BASE) {
if (ctrl->value) {
dev->freerun_mode = 1;
IONVID_DBG("ionvideo: set freerun mode\n");
}
}
return 0;
}
static int vidioc_g_parm(struct file *file, void *priv,
struct v4l2_streamparm *parms)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct v4l2_amlogic_parm *ap
= (struct v4l2_amlogic_parm *)parms->parm.raw_data;
if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
memset(ap, 0, sizeof(struct v4l2_amlogic_parm));
*ap = dev->am_parm;
return 0;
}
static const struct ionvideo_fmt *__get_format(u32 pixelformat)
{
const struct ionvideo_fmt *fmt;
unsigned int k;
for (k = 0; k < ARRAY_SIZE(formats); k++) {
fmt = &formats[k];
if (fmt->fourcc == pixelformat)
break;
}
if (k == ARRAY_SIZE(formats))
return NULL;
return &formats[k];
}
static const struct ionvideo_fmt *get_format(struct v4l2_format *f)
{
return __get_format(f->fmt.pix.pixelformat);
}
static DEFINE_SPINLOCK(devlist_lock);
static unsigned long ionvideo_devlist_lock(void)
{
unsigned long flags;
spin_lock_irqsave(&devlist_lock, flags);
return flags;
}
static void ionvideo_devlist_unlock(unsigned long flags)
{
spin_unlock_irqrestore(&devlist_lock, flags);
}
static LIST_HEAD(ionvideo_devlist);
static DEFINE_SPINLOCK(ion_states_lock);
static int ionvideo_vf_get_states(struct vframe_states *states)
{
int ret = -1;
unsigned long flags;
struct vframe_provider_s *vfp;
vfp = vf_get_provider(RECEIVER_NAME);
spin_lock_irqsave(&ion_states_lock, flags);
if (vfp && vfp->ops && vfp->ops->vf_states)
ret = vfp->ops->vf_states(states, vfp->op_arg);
spin_unlock_irqrestore(&ion_states_lock, flags);
return ret;
}
/* ------------------------------------------------------------------
* DMA and thread functions
* ------------------------------------------------------------------
*/
unsigned int get_ionvideo_debug(void)
{
return debug;
}
EXPORT_SYMBOL(get_ionvideo_debug);
static void videoc_omx_compute_pts(struct ionvideo_dev *dev,
struct vframe_s *vf)
{
if (dev->pts == 0) {
if (dev->is_omx_video_started == 0) {
dev->pts = dev->last_pts_us64
+ (DUR2PTS(vf->duration) * 100 / 9);
if ((vf->type & 0x1) == VIDTYPE_INTERLACE)
dev->pts += (DUR2PTS(vf->duration) * 100 / 9);
}
}
if (dev->is_omx_video_started)
dev->is_omx_video_started = 0;
dev->last_pts_us64 = dev->pts;
}
#if 0
static int canvas_is_valid(struct ionvideo_dev *dev, int index)
{
struct ppmgr2_device *ppd;
int ret = 0;
if (!dev)
return 0;
ppd = &dev->ppmgr2_dev;
if (!ppd)
return 0;
if (ppd->canvas_id[index] >= 0)
ret = 1;
if (ppd->canvas_id[index] < 0)
IONVID_ERR("cancas %d is invalid\n", ppd->canvas_id[index]);
return ret;
}
#endif
static int ionvideo_fillbuff(struct ionvideo_dev *dev,
struct v4l2_buffer *buf)
{
struct vframe_s *vf;
int ret = 0;
/* ------------------------------------------------------- */
#if 0
if (!canvas_is_valid(dev, vb->v4l2_buf.index))
return -1;
#endif
vf = vf_get(dev->vf_receiver_name);
if (!vf)
return -EAGAIN;
if (vf->flag & VFRAME_FLAG_SWITCHING_FENSE) {
vf_put(vf, dev->vf_receiver_name);
return -EAGAIN;
}
if (vf && dev->once_record == 1) {
dev->once_record = 0;
if ((vf->type & VIDTYPE_INTERLACE_BOTTOM) == 0x3)
dev->ppmgr2_dev.bottom_first = 1;
else
dev->ppmgr2_dev.bottom_first = 0;
}
if (dev->freerun_mode == 0) {
ret = ppmgr2_process(vf, &dev->ppmgr2_dev, buf->index);
if (ret) {
vf_put(vf, dev->vf_receiver_name);
return ret;
}
vf_put(vf, dev->vf_receiver_name);
} else {
if ((vf->type & 0x1) == VIDTYPE_INTERLACE) {
if ((dev->ppmgr2_dev.bottom_first
&& (vf->type & 0x2)) || (dev
->ppmgr2_dev
.bottom_first
== 0
&& ((vf
->type
& 0x2)
== 0)))
dev->pts = vf->pts_us64;
} else
dev->pts = vf->pts_us64;
/* for omx AdaptivePlayback */
if (vf->width <= ((dev->width + 31) & (~31)))
dev->ppmgr2_dev.dst_width = vf->width;
if (vf->height <= dev->height)
dev->ppmgr2_dev.dst_height = vf->height;
#if SCALE_4K_TO_1080P
if (vf->width > dev->width || vf->height > dev->height) {
if (vf->width*dev->height >= dev->width*vf->height) {
dev->ppmgr2_dev.dst_width = dev->width;
dev->ppmgr2_dev.dst_height = vf->height
* dev->width / vf->width;
} else {
dev->ppmgr2_dev.dst_width = vf->width
* dev->height / vf->height;
dev->ppmgr2_dev.dst_height = dev->height;
}
}
#endif
if ((dev->ppmgr2_dev.dst_width >= 1920) && (dev->ppmgr2_dev
.dst_height
>= 1080)
&& (vf->type & VIDTYPE_INTERLACE)) {
dev->ppmgr2_dev.dst_width = dev->ppmgr2_dev.dst_width
* scaling_rate
/ 100;
dev->ppmgr2_dev.dst_height = dev->ppmgr2_dev.dst_height
* scaling_rate
/ 100;
}
ret = ppmgr2_process(vf, &dev->ppmgr2_dev, buf->index);
if (ret) {
vf_put(vf, dev->vf_receiver_name);
return ret;
}
if (dev->wait_ge2d_timeout) {
IONVID_INFO("ppmgr2_process timeout\n");
dev->wait_ge2d_timeout = false;
return -EAGAIN;
}
dev->wait_ge2d_timeout = false;
videoc_omx_compute_pts(dev, vf);
buf->timecode.frames = 0;
dev->am_parm.signal_type = vf->signal_type;
dev->am_parm.master_display_colour
= vf->prop.master_display_colour;
vf_put(vf, dev->vf_receiver_name);
buf->timestamp.tv_sec = dev->pts >> 32;
buf->timestamp.tv_usec = dev->pts & 0xFFFFFFFF;
buf->timecode.type = dev->ppmgr2_dev.dst_width;
buf->timecode.flags = dev->ppmgr2_dev.dst_height;
}
/* ------------------------------------------------------- */
return 0;
}
static int ionvideo_size_changed(struct ionvideo_dev *dev, int aw, int ah)
{
v4l_bound_align_image(&aw, 48, MAX_WIDTH, 5, &ah, 32, MAX_HEIGHT, 0, 0);
dev->c_width = aw;
dev->c_height = ah;
if (aw != dev->width || ah != dev->height) {
dprintk(dev, 2, "Video frame size changed w:%d h:%d\n", aw, ah);
return -EAGAIN;
}
return 0;
}
static void ionvideo_thread_tick(struct ionvideo_dev *dev)
{
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
struct v4l2_buffer *buf;
struct vframe_s *vf;
int w, h;
dprintk(dev, 4, "Thread tick\n");
/* video seekTo clear list */
if (!dev)
return;
if (dev->active_state == ION_INACTIVE_REQ) {
dev->active_state = ION_INACTIVE;
complete(&dev->inactive_done);
}
if (dev->active_state == ION_INACTIVE)
return;
if (dev->receiver_register == 0)
return;
dev->wait_ge2d_timeout = false;
vf = vf_peek(dev->vf_receiver_name);
if (!vf) {
dev->vf_wait_cnt++;
/* msleep(5); */
usleep_range(1000, 2000);
return;
}
dev->ppmgr2_dev.dst_width = dev->width;
dev->ppmgr2_dev.dst_height = dev->height;
if ((vf->width >= 1920) && (vf->height >= 1080)
&& (vf->type & VIDTYPE_INTERLACE)) {
dev->ppmgr2_dev.dst_width = vf->width * scaling_rate / 100;
dev->ppmgr2_dev.dst_height = vf->height * scaling_rate / 100;
w = dev->ppmgr2_dev.dst_width;
h = dev->ppmgr2_dev.dst_height;
} else {
w = vf->width;
h = vf->height;
}
if (dev->freerun_mode == 0 && ionvideo_size_changed(dev, w, h)) {
/* msleep(10); */
usleep_range(4000, 5000);
return;
}
mutex_lock(&dev->mutex_input);
buf = v4l2q_peek(&dev->input_queue);
if (buf == NULL) {
dprintk(dev, 3, "No active queue to serve\n");
mutex_unlock(&dev->mutex_input);
schedule_timeout_interruptible(msecs_to_jiffies(20));
return;
}
mutex_unlock(&dev->mutex_input);
/* Fill buffer */
if (ionvideo_fillbuff(dev, buf))
return;
mutex_lock(&dev->mutex_input);
buf = v4l2q_pop(&dev->input_queue);
mutex_unlock(&dev->mutex_input);
dev->vf_wait_cnt = 0;
mutex_lock(&dev->mutex_output);
v4l2q_push(&dev->output_queue, buf);
dma_q->vb_ready++;
mutex_unlock(&dev->mutex_output);
dprintk(dev, 4, "[%p/%d] done\n", buf, buf->index);
}
#define frames_to_ms(frames) \
((frames * WAKE_NUMERATOR * 1000) / WAKE_DENOMINATOR)
static void ionvideo_sleep(struct ionvideo_dev *dev)
{
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
/* int timeout; */
DECLARE_WAITQUEUE(wait, current);
dprintk(dev, 4, "%s dma_q=0x%08lx\n", __func__, (unsigned long)dma_q);
add_wait_queue(&dma_q->wq, &wait);
if (kthread_should_stop())
goto stop_task;
/* Calculate time to wake up */
/* timeout = msecs_to_jiffies(frames_to_ms(1)); */
ionvideo_thread_tick(dev);
/* schedule_timeout_interruptible(timeout); */
stop_task: remove_wait_queue(&dma_q->wq, &wait);
try_to_freeze();
}
static int ionvideo_thread(void *data)
{
struct ionvideo_dev *dev = data;
dprintk(dev, 2, "thread started\n");
set_freezable();
dev->thread_stopped = 0;
for (;;) {
ionvideo_sleep(dev);
if (kthread_should_stop())
break;
}
dev->thread_stopped = 1;
wake_up_interruptible(&dev->wq);
dprintk(dev, 2, "thread: exit\n");
return 0;
}
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
dev->is_omx_video_started = 1;
dma_q->vb_ready = 0;
dprintk(dev, 2, "%s\n", __func__);
/* Resets frame counters */
dev->ms = 0;
/* dev->jiffies = jiffies; */
init_waitqueue_head(&dev->wq);
/* dma_q->ini_jiffies = jiffies; */
dma_q->kthread = kthread_run(ionvideo_thread, dev, dev->v4l2_dev.name);
if (IS_ERR(dma_q->kthread)) {
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return PTR_ERR(dma_q->kthread);
}
/* Wakes thread */
wake_up_interruptible(&dma_q->wq);
dprintk(dev, 2, "returning from %s\n", __func__);
return 0;
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
dprintk(dev, 2, "%s\n", __func__);
/* shutdown control thread */
if (dma_q->kthread) {
kthread_stop(dma_q->kthread);
dma_q->kthread = NULL;
}
wait_event_interruptible_timeout(dev->wq,
dev->thread_stopped != 0, HZ/5);
/*
* Typical driver might need to wait here until dma engine stops.
* In this case we can abort imiedetly, so it's just a noop.
*/
v4l2q_init(&dev->input_queue, IONVIDEO_POOL_SIZE + 1,
&dev->ionvideo_input_queue[0]);
v4l2q_init(&dev->output_queue, IONVIDEO_POOL_SIZE + 1,
&dev->ionvideo_output_queue[0]);
return 0;
}
/* ------------------------------------------------------------------
* IOCTL vidioc handling
* ------------------------------------------------------------------
*/
static int vidioc_open(struct file *file)
{
struct ionvideo_dev *dev = video_drvdata(file);
if (dev->fd_num > 0 || ppmgr2_init(&(dev->ppmgr2_dev)) < 0) {
pr_err("vidioc_open error\n");
return -EBUSY;
}
dev->fd_num++;
dev->pts = 0;
dev->c_width = 0;
dev->c_height = 0;
dev->once_record = 1;
dev->ppmgr2_dev.bottom_first = 0;
dev->skip_frames = 0;
dev->vf_wait_cnt = 0;
/*for libplayer osd*/
dev->freerun_mode = freerun_mode;
v4l2q_init(&dev->input_queue, IONVIDEO_POOL_SIZE + 1,
&dev->ionvideo_input_queue[0]);
v4l2q_init(&dev->output_queue, IONVIDEO_POOL_SIZE + 1,
&dev->ionvideo_output_queue[0]);
//dprintk(dev, 2, "vidioc_open\n");
IONVID_DBG("ionvideo open\n");
init_waitqueue_head(&dev->wq);
return 0;
}
static int vidioc_close(struct file *file)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
if (dma_q->kthread)
vidioc_streamoff(file, NULL, 0);
IONVID_DBG("vidioc_close!!!!\n");
ppmgr2_release(&(dev->ppmgr2_dev));
//dprintk(dev, 2, "vidioc_close\n");
IONVID_DBG("vidioc_close\n");
if (dev->fd_num > 0)
dev->fd_num--;
dev->once_record = 0;
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct ionvideo_dev *dev = video_drvdata(file);
strcpy(cap->driver, "ionvideo");
strcpy(cap->card, "ionvideo");
snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
dev->v4l2_dev.name);
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
| V4L2_CAP_READWRITE;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
const struct ionvideo_fmt *fmt;
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
fmt = &formats[f->index];
strlcpy(f->description, fmt->name, sizeof(f->description));
f->pixelformat = fmt->fourcc;
return 0;
}
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct ionvideo_dev *dev = video_drvdata(file);
if (dev->freerun_mode == 0) {
if (dev->c_width == 0 || dev->c_height == 0)
return -EINVAL;
f->fmt.pix.width = dev->c_width;
f->fmt.pix.height = dev->c_height;
} else {
f->fmt.pix.width = dev->width;
f->fmt.pix.height = dev->height;
}
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
f->fmt.pix.pixelformat = dev->fmt->fourcc;
f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt->depth) >> 3;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
if (dev->fmt->is_yuv)
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
else
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
return 0;
}
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct ionvideo_dev *dev = video_drvdata(file);
const struct ionvideo_fmt *fmt;
fmt = get_format(f);
if (!fmt) {
dprintk(dev, 1, "Fourcc format (0x%08x) unknown.\n",
f->fmt.pix.pixelformat);
return -EINVAL;
}
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 4,
&f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
if (fmt->is_yuv)
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
else
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
f->fmt.pix.priv = 0;
return 0;
}
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct ionvideo_dev *dev = video_drvdata(file);
int ret = vidioc_try_fmt_vid_cap(file, priv, f);
if (ret < 0)
return ret;
dev->fmt = get_format(f);
dev->pixelsize = dev->fmt->depth;
dev->width = f->fmt.pix.width;
dev->height = f->fmt.pix.height;
if ((dev->width == 0) || (dev->height == 0))
IONVID_ERR("ion buffer w/h info is invalid!!!!!!!!!!!\n");
return 0;
}
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
struct ppmgr2_device *ppmgr2_dev = &(dev->ppmgr2_dev);
struct dma_buf *dbuf = NULL;
struct ion_buffer *buffer = NULL;
struct sg_table *table = NULL;
struct page *page = NULL;
void *phy_addr = NULL;
dev->ionvideo_input[p->index] = *p;
mutex_lock(&dev->mutex_input);
v4l2q_push(&dev->input_queue, &(dev->ionvideo_input[p->index]));
mutex_unlock(&dev->mutex_input);
if (!ppmgr2_dev->phy_addr[p->index]) {
dbuf = dma_buf_get(p->m.fd);
buffer = dbuf->priv;
table = buffer->sg_table;
page = sg_page(table->sgl);
phy_addr = (void *)PFN_PHYS(page_to_pfn(page));
dma_buf_put(dbuf);
ppmgr2_dev->phy_addr[p->index] = phy_addr;
ppmgr2_dev->dst_buffer_width = ALIGN(dev->width, 32);
ppmgr2_dev->dst_buffer_height = dev->height;
ppmgr2_dev->ge2d_fmt = v4l_to_ge2d_format(dev->fmt->fourcc);
if (phy_addr) {
#if 0
ret = ppmgr2_canvas_config(ppmgr2_dev, dev->width,
dev->height,
dev->fmt->fourcc,
phy_addr, p->index);
#endif
} else {
return -ENOMEM;
}
}
wake_up_interruptible(&dma_q->wq);
return 0;
}
/*
static int vidioc_synchronization_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *p)
{
struct vb2_buffer *vb = NULL;
struct vb2_queue *q;
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_buffer *buf;
int ret = 0;
int d = 0;
unsigned long flags;
q = dev->vdev.queue;
if (dev->receiver_register) {
while (!list_empty(&q->done_list)) {
ret = vb2_ioctl_dqbuf(file, priv, p);
if (ret)
return ret;
ret = vb2_ioctl_qbuf(file, priv, p);
if (ret)
return ret;
}
IONVID_INFO("init to clear the done list buffer.done\n");
dev->receiver_register = 0;
dev->is_video_started = 0;
return -EAGAIN;
}
spin_lock_irqsave(&q->done_lock, flags);
if (list_empty(&q->done_list)) {
spin_unlock_irqrestore(&q->done_lock, flags);
return -EAGAIN;
}
vb = list_first_entry(&q->done_list, struct vb2_buffer,
done_entry);
spin_unlock_irqrestore(&q->done_lock, flags);
buf = container_of(vb, struct ionvideo_buffer, vb);
if (dev->is_video_started == 0) {
IONVID_INFO("Execute the VIDEO_START cmd. pts=%llx\n",
buf->pts);
tsync_avevent_locked(
VIDEO_START,
buf->pts ? buf->pts : timestamp_vpts_get());
d = 0;
dev->is_video_started = 1;
} else {
if (buf->pts == 0) {
buf->pts =
timestamp_vpts_get() + DUR2PTS(
buf->duration);
}
if (abs(timestamp_pcrscr_get() - buf->pts) >
tsync_vpts_discontinuity_margin()) {
tsync_avevent_locked(
VIDEO_TSTAMP_DISCONTINUITY,
buf->pts);
} else {
timestamp_vpts_set(buf->pts);
}
d = (buf->pts - timestamp_pcrscr_get());
}
if (d > 450) {
return -EAGAIN;
} else if (d < -11520) {
int s = 3;
while (s--) {
ret = vb2_ioctl_dqbuf(file, priv, p);
if (ret)
return ret;
if (buf->pts == 0) {
buf->pts =
timestamp_vpts_get() + DUR2PTS(
buf->duration);
}
if (abs(timestamp_pcrscr_get() - buf->pts) >
tsync_vpts_discontinuity_margin()) {
tsync_avevent_locked(
VIDEO_TSTAMP_DISCONTINUITY,
buf->pts);
} else {
timestamp_vpts_set(buf->pts);
}
if (list_empty(&q->done_list))
break;
ret = vb2_ioctl_qbuf(file, priv, p);
if (ret)
return ret;
dev->skip_frames++;
}
dprintk(dev, 1, "s:%u\n", dev->skip_frames);
} else {
ret = vb2_ioctl_dqbuf(file, priv, p);
if (ret)
return ret;
}
p->timestamp.tv_sec = 0;
p->timestamp.tv_usec = timestamp_vpts_get();
return 0;
}
*/
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct ionvideo_dev *dev = video_drvdata(file);
struct ionvideo_dmaqueue *dma_q = &dev->vidq;
struct v4l2_buffer *out_put = NULL;
/*
if (dev->freerun_mode == 0)
return vidioc_synchronization_dqbuf(file, priv, p);
*/
mutex_lock(&dev->mutex_output);
out_put = v4l2q_pop(&dev->output_queue);
if (out_put != NULL) {
dma_q->vb_ready--;
*p = *out_put;
} else {
mutex_unlock(&dev->mutex_output);
return -EAGAIN;
}
mutex_unlock(&dev->mutex_output);
return 0;
}
#define NUM_INPUTS 10
/* ------------------------------------------------------------------
* File operations for the device
* ------------------------------------------------------------------
*/
static const struct v4l2_file_operations ionvideo_v4l2_fops = {
.owner = THIS_MODULE,
.open = vidioc_open,
.release = vidioc_close,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,/* V4L2 ioctl handler */
.mmap = vb2_fop_mmap,
};
static const struct v4l2_ioctl_ops ionvideo_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_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_g_parm = vidioc_g_parm,
};
static const struct video_device ionvideo_template = {
.name = "ionvideo",
.fops = &ionvideo_v4l2_fops,
.ioctl_ops = &ionvideo_ioctl_ops,
.release = video_device_release,
};
/* -----------------------------------------------------------------
* Initialization and module stuff
* -----------------------------------------------------------------
*/
/* struct vb2_dc_conf * ionvideo_dma_ctx = NULL; */
static int ionvideo_v4l2_release(void)
{
struct ionvideo_dev *dev;
struct list_head *list;
unsigned long flags;
flags = ionvideo_devlist_lock();
while (!list_empty(&ionvideo_devlist)) {
list = ionvideo_devlist.next;
list_del(list);
ionvideo_devlist_unlock(flags);
dev = list_entry(list, struct ionvideo_dev, ionvideo_devlist);
v4l2_info(&dev->v4l2_dev, "unregistering %s\n",
video_device_node_name(&dev->vdev));
video_unregister_device(&dev->vdev);
v4l2_device_unregister(&dev->v4l2_dev);
kfree(dev);
flags = ionvideo_devlist_lock();
}
/* vb2_dma_contig_cleanup_ctx(ionvideo_dma_ctx); */
ionvideo_devlist_unlock(flags);
return 0;
}
static int video_receiver_event_fun(int type, void *data, void *private_data)
{
struct ionvideo_dev *dev = (struct ionvideo_dev *)private_data;
int timeout = 0;
if (type == VFRAME_EVENT_PROVIDER_UNREG) {
dev->receiver_register = 0;
dev->is_omx_video_started = 0;
if (dev->active_state == ION_ACTIVE) {
/*if player killed thread may have exit.*/
dev->active_state = ION_INACTIVE_REQ;
dev->wait_ge2d_timeout = false;
timeout = wait_for_completion_timeout(
&dev->inactive_done,
msecs_to_jiffies(200));
if (!timeout) {
IONVID_INFO("unreg:wait timeout\n");
dev->wait_ge2d_timeout = true;
}
}
/*tsync_avevent(VIDEO_STOP, 0);*/
pr_info("unreg:ionvideo\n");
} else if (type == VFRAME_EVENT_PROVIDER_REG) {
dev->is_omx_video_started = 1;
dev->ppmgr2_dev.interlaced_num = 0;
dev->active_state = ION_ACTIVE;
init_completion(&dev->inactive_done);
dev->receiver_register = 1;
pr_info("reg:ionvideo\n");
} else if (type == VFRAME_EVENT_PROVIDER_QUREY_STATE) {
if (dev->vf_wait_cnt > 1)
return RECEIVER_INACTIVE;
return RECEIVER_ACTIVE;
} else if (type == VFRAME_EVENT_PROVIDER_FR_HINT) {
#ifdef CONFIG_AM_VOUT
if ((data != NULL) && (ionvideo_seek_flag == 0))
set_vframe_rate_hint((unsigned long)(data));
#endif
} else if (type == VFRAME_EVENT_PROVIDER_FR_END_HINT) {
#ifdef CONFIG_AM_VOUT
if (ionvideo_seek_flag == 0) {
set_vframe_rate_end_hint();
}
#endif
}
return 0;
}
static const struct vframe_receiver_op_s video_vf_receiver = {
.event_cb = video_receiver_event_fun
};
static int __init ionvideo_create_instance(int inst)
{
struct ionvideo_dev *dev;
struct video_device *vfd;
int ret;
unsigned long flags;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
"%s-%03d", IONVIDEO_MODULE_NAME, inst);
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
if (ret)
goto free_dev;
dev->fmt = &formats[0];
dev->width = 640;
dev->height = 480;
dev->pixelsize = dev->fmt->depth;
dev->fd_num = 0;
dev->ionvideo_v4l_num = inst + video_nr_base;
dev->ppmgr2_dev.ge2d_canvas_mutex = &ppmgr2_ge2d_canvas_mutex;
/* initialize locks */
spin_lock_init(&dev->slock);
mutex_init(&dev->mutex);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
init_waitqueue_head(&dev->vidq.wq);
dev->vidq.pdev = dev;
vfd = &dev->vdev;
*vfd = ionvideo_template;
vfd->dev_debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
/*
* Provide a mutex to v4l2 core. It will be used to protect
* all fops and v4l2 ioctls.
*/
ret = video_register_device(vfd, VFL_TYPE_GRABBER,
inst + video_nr_base);
if (ret < 0)
goto unreg_dev;
video_set_drvdata(vfd, dev);
dev->inst = inst;
snprintf(dev->vf_receiver_name, ION_VF_RECEIVER_NAME_SIZE,
(inst == 0) ? RECEIVER_NAME : RECEIVER_NAME ".%x",
inst & 0xff);
vf_receiver_init(&dev->video_vf_receiver,
dev->vf_receiver_name,
&video_vf_receiver, dev);
vf_reg_receiver(&dev->video_vf_receiver);
v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
video_device_node_name(vfd));
/* add to device list */
flags = ionvideo_devlist_lock();
list_add_tail(&dev->ionvideo_devlist, &ionvideo_devlist);
ionvideo_devlist_unlock(flags);
mutex_init(&dev->mutex_input);
mutex_init(&dev->mutex_output);
return 0;
unreg_dev:
v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
kfree(dev);
return ret;
}
int ionvideo_assign_map(char **receiver_name, int *inst)
{
unsigned long flags;
struct ionvideo_dev *dev = NULL;
struct list_head *p;
flags = ionvideo_devlist_lock();
list_for_each(p, &ionvideo_devlist) {
dev = list_entry(p, struct ionvideo_dev, ionvideo_devlist);
if (dev->inst == *inst) {
*receiver_name = dev->vf_receiver_name;
ionvideo_devlist_unlock(flags);
return 0;
}
}
ionvideo_devlist_unlock(flags);
return -ENODEV;
}
EXPORT_SYMBOL(ionvideo_assign_map);
int ionvideo_alloc_map(int *inst)
{
unsigned long flags;
struct ionvideo_dev *dev = NULL;
struct list_head *p;
flags = ionvideo_devlist_lock();
list_for_each(p, &ionvideo_devlist) {
dev = list_entry(p, struct ionvideo_dev, ionvideo_devlist);
if ((dev->inst >= 0) && (!dev->mapped)) {
dev->mapped = true;
*inst = dev->inst;
ionvideo_devlist_unlock(flags);
return 0;
}
}
ionvideo_devlist_unlock(flags);
return -ENODEV;
}
void ionvideo_release_map(int inst)
{
unsigned long flags;
struct ionvideo_dev *dev = NULL;
struct list_head *p;
flags = ionvideo_devlist_lock();
list_for_each(p, &ionvideo_devlist) {
dev = list_entry(p, struct ionvideo_dev, ionvideo_devlist);
if ((dev->inst == inst) && (dev->mapped)) {
dev->mapped = false;
pr_debug("ionvideo_release_map %d OK\n", dev->inst);
break;
}
}
ionvideo_devlist_unlock(flags);
}
static ssize_t vframe_states_show(struct class *class,
struct class_attribute *attr, char *buf)
{
int ret = 0;
struct vframe_states states;
/* unsigned long flags; */
if (ionvideo_vf_get_states(&states) == 0) {
ret += sprintf(buf + ret,
"vframe_pool_size=%d\n", states.vf_pool_size);
ret += sprintf(buf + ret, "vframe buf_free_num=%d\n",
states.buf_free_num);
ret += sprintf(buf + ret, "vframe buf_recycle_num=%d\n",
states.buf_recycle_num);
ret += sprintf(buf + ret, "vframe buf_avail_num=%d\n",
states.buf_avail_num);
} else {
ret += sprintf(buf + ret, "vframe no states\n");
}
return ret;
}
static ssize_t scaling_rate_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return snprintf(buf, 80, "current scaling rate is %d\n", scaling_rate);
}
static ssize_t scaling_rate_write(struct class *cla,
struct class_attribute *attr,
const char *buf, size_t count)
{
ssize_t size;
char *endp = NULL;
unsigned long tmp;
int ret;
ret = kstrtoul(buf, 0, &tmp);
if (ret != 0) {
IONVID_ERR("ERROR parsing string:%s to unsigned long\n", buf);
return ret;
}
/* scaling_rate = simple_strtoul(buf, &endp, 0); */
scaling_rate = tmp;
size = endp - buf;
return count;
}
static ssize_t scaling_ionvideo_seek_flag_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", ionvideo_seek_flag);
}
static ssize_t scaling_ionvideo_seek_flag_write(struct class *cla,
struct class_attribute *attr,
const char *buf, size_t count)
{
size_t r;
r = kstrtoint(buf, 0, &ionvideo_seek_flag);
if (r != 0)
return -EINVAL;
return count;
}
static struct class_attribute ion_video_class_attrs[] = {
__ATTR_RO(vframe_states),
__ATTR(scaling_rate,
0644,
scaling_rate_show,
scaling_rate_write),
__ATTR(ionvideo_seek_flag,
0644,
scaling_ionvideo_seek_flag_show,
scaling_ionvideo_seek_flag_write),
__ATTR_NULL };
static struct class ionvideo_class = {.name = "ionvideo", .class_attrs =
ion_video_class_attrs, };
static int ionvideo_open(struct inode *inode, struct file *file)
{
return 0;
}
static int ionvideo_release(struct inode *inode, struct file *file)
{
return 0;
}
static long ionvideo_ioctl(struct file *file,
unsigned int cmd,
ulong arg)
{
long ret = 0;
void __user *argp = (void __user *)arg;
switch (cmd) {
case IONVIDEO_IOCTL_ALLOC_ID:{
u32 ionvideo_id = 0;
ret = ionvideo_alloc_map(&ionvideo_id);
if (ret != 0)
break;
put_user(ionvideo_id, (u32 __user *)argp);
}
break;
case IONVIDEO_IOCTL_FREE_ID:{
u32 ionvideo_id;
get_user(ionvideo_id, (u32 __user *)argp);
ionvideo_release_map(ionvideo_id);
}
break;
default:
return -EINVAL;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long ionvideo_compat_ioctl(struct file *file,
unsigned int cmd,
ulong arg)
{
long ret = 0;
ret = ionvideo_ioctl(file, cmd, (ulong)compat_ptr(arg));
return ret;
}
#endif
static const struct file_operations ionvideo_fops = {
.owner = THIS_MODULE,
.open = ionvideo_open,
.release = ionvideo_release,
.unlocked_ioctl = ionvideo_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ionvideo_compat_ioctl,
#endif
.poll = NULL,
};
/* This routine allocates from 1 to n_devs virtual drivers.
* The real maximum number of virtual drivers will depend on how many drivers
* will succeed. This is limited to the maximum number of devices that
* videodev supports, which is equal to VIDEO_NUM_DEVICES.
*/
static int ionvideo_driver_probe(struct platform_device *pdev)
{
int ret = 0, i;
if (n_devs <= 0)
n_devs = 1;
mutex_init(&ppmgr2_ge2d_canvas_mutex);
for (i = 0; i < n_devs; i++) {
ret = ionvideo_create_instance(i);
if (ret) {
/* If some instantiations succeeded, keep driver */
if (i)
ret = 0;
break;
}
}
if (ret < 0) {
IONVID_ERR("ionvideo: error %d while loading driver\n", ret);
return ret;
}
IONVID_INFO("Video Technology Magazine Ion Video\n");
IONVID_INFO("Capture Board ver %s successfully loaded\n",
IONVIDEO_VERSION);
/* n_devs will reflect the actual number of allocated devices */
n_devs = i;
return ret;
}
static int ionvideo_drv_remove(struct platform_device *pdev)
{
ionvideo_v4l2_release();
return 0;
}
static const struct of_device_id ionvideo_dt_match[] = {
{
.compatible = "amlogic, ionvideo",
},
{}
};
/* general interface for a linux driver .*/
static struct platform_driver ionvideo_drv = {
.probe = ionvideo_driver_probe,
.remove = ionvideo_drv_remove,
.driver = {
.name = "ionvideo",
.owner = THIS_MODULE,
.of_match_table = ionvideo_dt_match,
}
};
static int __init ionvideo_init(void)
{
int ret = -1;
struct device *devp;
ret = class_register(&ionvideo_class);
if (ret < 0)
return ret;
ret = register_chrdev(IONVIDEO_MAJOR, "ionvideo", &ionvideo_fops);
if (ret < 0) {
pr_err("Can't allocate major for ionvideo device\n");
goto error1;
}
devp = device_create(&ionvideo_class,
NULL,
MKDEV(IONVIDEO_MAJOR, 0),
NULL,
IONVIDEO_DEVICE_NAME);
if (IS_ERR(devp)) {
pr_err("failed to create ionvideo device node\n");
ret = PTR_ERR(devp);
return ret;
}
if (platform_driver_register(&ionvideo_drv)) {
pr_err("Failed to register ionvideo driver\n");
return -ENODEV;
}
return 0;
error1:
unregister_chrdev(IONVIDEO_MAJOR, "ionvideo");
class_unregister(&ionvideo_class);
return ret;
}
static void __exit ionvideo_exit(void)
{
platform_driver_unregister(&ionvideo_drv);
device_destroy(&ionvideo_class, MKDEV(IONVIDEO_MAJOR, 0));
unregister_chrdev(IONVIDEO_MAJOR, IONVIDEO_DEVICE_NAME);
class_unregister(&ionvideo_class);
}
MODULE_DESCRIPTION("Video Technology Magazine Ion Video Capture Board");
MODULE_AUTHOR("Amlogic, Shuai Cao<shuai.cao@amlogic.com>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION(IONVIDEO_VERSION);
module_init(ionvideo_init);
module_exit(ionvideo_exit);