blob: 67be5a7e650d3e3510a8f133084d0fd9c9416e20 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
#include <linux/platform_device.h>
#include <linux/amlogic/media/v4l_util/videobuf-res.h>
#include <media/videobuf2-core.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <linux/types.h>
#include <linux/amlogic/media/canvas/canvas.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 <linux/amlogic/media/ge2d/ge2d.h>
#include <linux/amlogic/media/frame_sync/timestamp.h>
#include <linux/kernel.h>
#include <linux/amlogic/media/frame_sync/tsync.h>
#include "../utils/vfp.h"
#include "amlvideo.h"
#define AVMLVIDEO_MODULE_NAME "amlvideo"
#define KERNEL_ATRACE_TAG KERNEL_ATRACE_TAG_AMLVIDEO
#include <trace/events/meson_atrace.h>
#define AMLVIDEO_INFO(fmt, args...) pr_info("amlvid:info: " fmt " ", ## args)
#define AMLVIDEO_DBG(fmt, args...) pr_debug("amlvid:dbg: " fmt " ", ## args)
#define AMLVIDEO_WARN(fmt, args...) pr_warn("amlvid:warn: " fmt " ", ## args)
#define AMLVIDEO_ERR(fmt, args...) pr_err("amlvid:err: " fmt " ", ## args)
#define AMLVIDEO_MAJOR_VERSION 0
#define AMLVIDEO_MINOR_VERSION 7
#define AMLVIDEO_RELEASE 1
#define AMLVIDEO_VERSION \
KERNEL_VERSION(AMLVIDEO_MAJOR_VERSION, \
AMLVIDEO_MINOR_VERSION, AMLVIDEO_RELEASE)
#define MAGIC_RE_MEM 0x123039dc
#define RECEIVER_NAME "amlvideo"
#define PROVIDER_NAME "amlvideo"
#define RECEIVER_NAME_PIP "aml_video"
#define PROVIDER_NAME_PIP "aml_video"
#define AMLVIDEO_POOL_SIZE 16
/*extern bool omx_secret_mode;*/
static inline u32 dur2pts(u32 duration)
{
return (duration - (duration >> 4));
}
#define DUR2PTS_RM(x) ((x) & 0xf)
//MODULE_AUTHOR("amlogic-sh");
//MODULE_LICENSE("GPL");
/* static u32 vpts_remainder; */
static unsigned int video_nr_base = 10;
static unsigned int video_nr_base_second = 23;
/* module_param(video_nr_base, uint, 0644); */
/* MODULE_PARM_DESC(video_nr_base, "videoX start number, 10 is defaut"); */
#ifdef CONFIG_AMLOGIC_MEDIA_MULTI_DEC
static unsigned int n_devs = 2;
#else
static unsigned int n_devs = 1;
#endif
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 int video_receiver_event_fun(int type, void *data, void*);
#define dprintk(dev, level, fmt, arg...) \
v4l2_dbg((level), debug, &(dev)->v4l2_dev, fmt, ## arg)
/* ------------------------------------------------------------------
* Basic structures
* ------------------------------------------------------------------
*/
static struct vivi_fmt formats[] = {
{
.name = "RGB888 (24)",
.fourcc = V4L2_PIX_FMT_RGB24, /* 24 RGB-8-8-8 */
.depth = 24,
},
{
.name = "RGBA8888 (32)",
.fourcc = V4L2_PIX_FMT_RGB32, /* 24 RGBA-8-8-8-8 */
.depth = 32,
},
{
.name = "12 Y/CbCr 4:2:0",
.fourcc = V4L2_PIX_FMT_NV12,
.depth = 12,
},
{
.name = "21 Y/CbCr 4:2:0",
.fourcc = V4L2_PIX_FMT_NV21,
.depth = 12,
},
{
.name = "RGB565 (BE)",
.fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
.depth = 16,
},
};
/* -----------------------------------------------------------------
* provider operations
* -----------------------------------------------------------------
*/
static struct vframe_s *amlvideo_vf_peek(void *op_arg)
{
struct vivi_dev *dev = (struct vivi_dev *)op_arg;
return vfq_peek(&dev->q_ready);
}
static struct vframe_s *amlvideo_vf_get(void *op_arg)
{
struct vframe_s *vf;
struct vivi_dev *dev = (struct vivi_dev *)op_arg;
vf = vfq_pop(&dev->q_ready);
ATRACE_COUNTER(dev->v4l2_dev.name, vfq_level(&dev->q_ready));
return vf;
}
static void amlvideo_vf_put(struct vframe_s *vf, void *op_arg)
{
struct vivi_dev *dev = (struct vivi_dev *)op_arg;
vf_put(vf, dev->vf_receiver_name);
vf_notify_provider(dev->vf_receiver_name, VFRAME_EVENT_RECEIVER_PUT,
NULL);
}
static int amlvideo_event_cb(int type, void *data, void *private_data)
{
/* printk("ionvideo_event_cb_type=%d\n",type); */
if (type & VFRAME_EVENT_RECEIVER_PUT) {
/* printk("video put, avail=%d\n", vfq_level(&q_ready) ); */
} else if (type & VFRAME_EVENT_RECEIVER_GET) {
/* printk("video get, avail=%d\n", vfq_level(&q_ready) ); */
} else if (type & VFRAME_EVENT_RECEIVER_FRAME_WAIT) {
/* up(&thread_sem); */
/* printk("receiver is waiting\n"); */
} else if (type & VFRAME_EVENT_RECEIVER_FRAME_WAIT) {
/* printk("frame wait\n"); */
}
return 0;
}
static int amlvideo_vf_states(struct vframe_states *states, void *op_arg)
{
/* unsigned long flags; */
/* spin_lock_irqsave(&lock, flags); */
struct vivi_dev *dev = (struct vivi_dev *)op_arg;
states->vf_pool_size = AMLVIDEO_POOL_SIZE;
states->buf_recycle_num = 0;
states->buf_free_num = AMLVIDEO_POOL_SIZE - vfq_level(&dev->q_ready);
states->buf_avail_num = vfq_level(&dev->q_ready);
/* spin_unlock_irqrestore(&lock, flags); */
return 0;
}
static const struct vframe_operations_s amlvideo_vf_provider = {
.peek = amlvideo_vf_peek,
.get = amlvideo_vf_get,
.put = amlvideo_vf_put,
.event_cb = amlvideo_event_cb,
.vf_states = amlvideo_vf_states,
};
static struct vivi_fmt *get_format(struct v4l2_format *f)
{
struct vivi_fmt *fmt;
unsigned int k;
for (k = 0; k < ARRAY_SIZE(formats); k++) {
fmt = &formats[k];
if (fmt->fourcc == f->fmt.pix.pixelformat)
break;
}
if (k == ARRAY_SIZE(formats))
return NULL;
return &formats[k];
}
static int video_receiver_event_fun(int type, void *data, void *private_data)
{
struct vframe_states states;
struct vivi_dev *dev = (struct vivi_dev *)private_data;
if (type == VFRAME_EVENT_PROVIDER_UNREG) {
AMLVIDEO_DBG("AML:VFRAME_EVENT_PROVIDER_UNREG\n");
mutex_lock(&dev->vf_mutex);
if (vf_get_receiver(dev->vf_provider_name)) {
AMLVIDEO_DBG("unreg:amlvideo\n");
vf_unreg_provider(&dev->video_vf_prov);
if (dev->inst == 0)
omx_secret_mode = false;
}
dev->first_frame = 0;
vfq_init(&dev->q_ready, AMLVIDEO_POOL_SIZE + 1,
&dev->amlvideo_pool_ready[0]);
mutex_unlock(&dev->vf_mutex);
}
if (type == VFRAME_EVENT_PROVIDER_REG) {
AMLVIDEO_DBG("AML:VFRAME_EVENT_PROVIDER_REG\n");
mutex_lock(&dev->vf_mutex);
dev->vf = NULL;
dev->first_frame = 0;
dev->frame_num = 0;
mutex_unlock(&dev->vf_mutex);
} else if (type == VFRAME_EVENT_PROVIDER_QUREY_STATE) {
amlvideo_vf_states(&states, dev);
if (states.buf_avail_num > 0)
return RECEIVER_ACTIVE;
if (vf_notify_receiver
(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_QUREY_STATE,
NULL) == RECEIVER_ACTIVE)
return RECEIVER_ACTIVE;
return RECEIVER_INACTIVE;
/*break;*/
} else if (type == VFRAME_EVENT_PROVIDER_START) {
AMLVIDEO_DBG("AML:VFRAME_EVENT_PROVIDER_START\n");
mutex_lock(&dev->vf_mutex);
if (vf_get_receiver(dev->vf_provider_name)) {
struct vframe_receiver_s *aaa = vf_get_receiver
(dev->vf_provider_name);
if (aaa)
AMLVIDEO_DBG("aaa->name=%s", aaa->name);
if (dev->inst == 0)
omx_secret_mode = true;
vfq_init(&dev->q_ready, AMLVIDEO_POOL_SIZE + 1,
&dev->amlvideo_pool_ready[0]);
vf_provider_init(&dev->video_vf_prov,
dev->vf_provider_name,
&amlvideo_vf_provider, dev);
vf_reg_provider(&dev->video_vf_prov);
vf_notify_receiver(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_START,
NULL);
}
mutex_unlock(&dev->vf_mutex);
} else if (type == VFRAME_EVENT_PROVIDER_FR_HINT) {
vf_notify_receiver(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_FR_HINT, data);
} else if (type == VFRAME_EVENT_PROVIDER_FR_END_HINT) {
vf_notify_receiver(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_FR_END_HINT, data);
} else if (type == VFRAME_EVENT_PROVIDER_RESET) {
dev->first_frame = 0;
vfq_init(&dev->q_ready, AMLVIDEO_POOL_SIZE + 1,
&dev->amlvideo_pool_ready[0]);
vf_notify_receiver(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_RESET, data);
} else if (type == VFRAME_EVENT_PROVIDER_VFRAME_READY) {
if (vf_peek(dev->vf_receiver_name))
wake_up_interruptible(&dev->wq);
}
return 0;
}
static const struct vframe_receiver_op_s video_vf_receiver = {
.event_cb = video_receiver_event_fun
};
/* ------------------------------------------------------------------
* Videobuf operations
* ------------------------------------------------------------------
*/
static int vidioc_g_parm(struct file *file, void *priv,
struct v4l2_streamparm *parms)
{
struct vivi_dev *dev = video_drvdata(file);
if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
memcpy(parms->parm.raw_data, (u8 *)&dev->am_parm,
sizeof(struct v4l2_amlogic_parm));
return 0;
}
static int buffer_setup(struct videobuf_queue *vq, unsigned int *count,
unsigned int *size)
{
struct videobuf_res_privdata *res = (struct videobuf_res_privdata *)vq
->priv_data;
struct vivi_fh *fh = (struct vivi_fh *)res->priv;
struct vivi_dev *dev = fh->dev;
*size = (fh->width * fh->height * fh->fmt->depth) >> 3;
if (*count == 0)
*count = 32;
while (*size * *count > vid_limit * 1024 * 1024)
(*count)--;
dprintk(dev, 1, "%s, count=%d, size=%d\n", __func__, *count, *size);
return 0;
}
static void free_buffer(struct videobuf_queue *vq, struct vivi_buffer *buf)
{
struct videobuf_res_privdata *res = (struct videobuf_res_privdata *)vq
->priv_data;
struct vivi_fh *fh = (struct vivi_fh *)res->priv;
struct vivi_dev *dev = fh->dev;
dprintk(dev, 1, "%s, state: %i\n", __func__, buf->vb.state);
videobuf_waiton(vq, &buf->vb, 0, 0);
if (in_interrupt())
WARN_ON(1);
videobuf_res_free(vq, &buf->vb);
dprintk(dev, 1, "%s: freed\n", __func__);
buf->vb.state = VIDEOBUF_NEEDS_INIT;
}
#define NORM_MAXW 2000
#define NORM_MAXH 1600
static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct videobuf_res_privdata *res = (struct videobuf_res_privdata *)vq
->priv_data;
struct vivi_fh *fh = (struct vivi_fh *)res->priv;
struct vivi_dev *dev = fh->dev;
struct vivi_buffer
*buf = container_of(vb, struct vivi_buffer, vb);
int rc;
dprintk(dev, 1, "%s, field=%d\n", __func__, field);
WARN_ON(!fh->fmt);
if (fh->width < 48 ||
fh->width > NORM_MAXW ||
fh->height < 32 ||
fh->height > NORM_MAXH)
return -EINVAL;
buf->vb.size = (fh->width * fh->height * fh->fmt->depth) >> 3;
if (buf->vb.baddr != 0 && buf->vb.bsize < buf->vb.size)
return -EINVAL;
/* These properties only change when queue is idle, see s_fmt */
buf->fmt = fh->fmt;
buf->vb.width = fh->width;
buf->vb.height = fh->height;
buf->vb.field = field;
if (buf->vb.state == VIDEOBUF_NEEDS_INIT) {
rc = videobuf_iolock(vq, &buf->vb, NULL);
if (rc < 0)
goto fail;
}
buf->vb.state = VIDEOBUF_PREPARED;
return 0;
fail: free_buffer(vq, buf);
return rc;
}
static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
struct vivi_buffer
*buf = container_of(vb, struct vivi_buffer, vb);
struct videobuf_res_privdata *res = (struct videobuf_res_privdata *)vq
->priv_data;
struct vivi_fh *fh = (struct vivi_fh *)res->priv;
struct vivi_dev *dev = fh->dev;
struct vivi_dmaqueue *vidq = &dev->vidq;
dprintk(dev, 1, "%s\n", __func__);
buf->vb.state = VIDEOBUF_QUEUED;
list_add_tail(&buf->vb.queue, &vidq->active);
}
static void buffer_release(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
{
struct vivi_buffer
*buf = container_of(vb, struct vivi_buffer, vb);
free_buffer(vq, buf);
}
static struct videobuf_queue_ops vivi_video_qops = {
.buf_setup = buffer_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.buf_release = buffer_release,
};
/* ------------------------------------------------------------------
* IOCTL vidioc handling
* ------------------------------------------------------------------
*/
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct vivi_fh *fh = priv;
struct vivi_dev *dev = fh->dev;
strcpy(cap->driver, "amlvideo");
strcpy(cap->card, "amlvideo");
strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
cap->version = AMLVIDEO_VERSION;
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)
{
struct vivi_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 vivi_fh *fh = priv;
f->fmt.pix.width = fh->width;
f->fmt.pix.height = fh->height;
f->fmt.pix.field = fh->vb_vidq.field;
f->fmt.pix.pixelformat = fh->fmt->fourcc;
f->fmt.pix.bytesperline = (f->fmt.pix.width * fh->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 vivi_fh *fh = priv;
int ret = 0;
fh->fmt = get_format(f);
fh->width = f->fmt.pix.width;
fh->height = f->fmt.pix.height;
fh->vb_vidq.field = f->fmt.pix.field;
fh->type = f->type;
return ret;
}
/*FIXME: This seems to be generic enough to be at videodev2 */
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivi_fh *fh = priv;
int ret = 0;
fh->fmt = get_format(f);
fh->width = f->fmt.pix.width;
fh->height = f->fmt.pix.height;
fh->vb_vidq.field = f->fmt.pix.field;
fh->type = f->type;
return ret;
}
static int vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct vivi_fh *fh = priv;
return videobuf_reqbufs(&fh->vb_vidq, p);
}
static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct vivi_fh *fh = priv;
return videobuf_querybuf(&fh->vb_vidq, p);
}
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return 0;
}
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct vivi_dev *dev = video_drvdata(file);
int ret = 0;
u64 pts_us64 = 0;
u64 pts_tmp;
struct vframe_s *next_vf;
if (vfq_level(&dev->q_ready) > AMLVIDEO_POOL_SIZE - 1)
return -EAGAIN;
if (!vf_peek(dev->vf_receiver_name))
return -EAGAIN;
mutex_lock(&dev->vf_mutex);
dev->vf = vf_get(dev->vf_receiver_name);
if (!dev->vf) {
/* printk("%s, %s, %d\n", __FILE__, __FUNCTION__, __LINE__); */
mutex_unlock(&dev->vf_mutex);
return -EAGAIN;
}
if (dev->vf->index == 0xFFFFFFFF) {
pr_info("%s: Invalid vf\n", __func__);
mutex_unlock(&dev->vf_mutex);
return -EAGAIN;
}
dev->vf->omx_index = dev->frame_num;
if (dev->vf->type & VIDTYPE_V4L_EOS) {
ret = -EAGAIN;
goto dqbuf_done;
}
dev->am_parm.signal_type = dev->vf->signal_type;
dev->am_parm.master_display_colour =
dev->vf->prop.master_display_colour;
if (dev->vf->hdr10p_data_size > 0 && dev->vf->hdr10p_data_buf) {
if (dev->vf->hdr10p_data_size <= 128) {
dev->am_parm.hdr10p_data_size =
dev->vf->hdr10p_data_size;
memcpy(dev->am_parm.hdr10p_data_buf,
dev->vf->hdr10p_data_buf,
dev->vf->hdr10p_data_size);
} else {
pr_info("amlvideo: hdr10+ data size is %d, skip it!\n",
dev->vf->hdr10p_data_size);
dev->am_parm.hdr10p_data_size = 0;
}
} else {
dev->am_parm.hdr10p_data_size = 0;
}
if (dev->vf->pts_us64) {
dev->first_frame = 1;
pts_us64 = dev->vf->pts_us64;
} else if (dev->first_frame == 0) {
dev->first_frame = 1;
pts_us64 = 0;
} else {
pts_tmp = dur2pts(dev->vf->duration) * 100;
do_div(pts_tmp, 9);
pts_us64 = dev->last_pts_us64
+ pts_tmp;
pts_tmp = pts_us64 * 9;
do_div(pts_tmp, 100);
dev->vf->pts = pts_tmp;
/*AMLVIDEO_WARN("pts= %d, dev->vf->duration= %d\n",*/
/*dev->vf->pts, (dur2pts(dev->vf->duration)));*/
}
next_vf = vf_peek(dev->vf_receiver_name);
dev->vf->next_vf_pts_valid = (next_vf ? 1 : 0);
if (dev->vf->next_vf_pts_valid)
dev->vf->next_vf_pts = next_vf->pts;
p->timestamp.tv_sec = pts_us64 >> 32;
p->timestamp.tv_usec = pts_us64 & 0xFFFFFFFF;
dev->last_pts_us64 = pts_us64;
if ((dev->vf->type & VIDTYPE_COMPRESS) != 0) {
p->timecode.type = dev->vf->compWidth;
p->timecode.flags = dev->vf->compHeight;
} else {
p->timecode.type = dev->vf->width;
p->timecode.flags = dev->vf->height;
}
dqbuf_done:
vfq_push(&dev->q_ready, dev->vf);
ATRACE_COUNTER(dev->v4l2_dev.name, vfq_level(&dev->q_ready));
p->index = 0;
p->sequence = dev->frame_num++;
mutex_unlock(&dev->vf_mutex);
vf_notify_receiver(dev->vf_provider_name,
VFRAME_EVENT_PROVIDER_VFRAME_READY,
NULL);
return ret;
}
#ifdef CONFIG_VIDEO_V4L1_COMPAT
static int vidiocgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf)
{
struct vivi_fh *fh = priv;
return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8);
}
#endif
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct vivi_fh *fh = priv;
int ret;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || i != fh->type)
return -EINVAL;
ret = videobuf_streamon(&fh->vb_vidq);
if (ret == 0)
fh->is_streamed_on = 1;
return ret;
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct vivi_fh *fh = priv;
int ret;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || i != fh->type)
return -EINVAL;
ret = videobuf_streamoff(&fh->vb_vidq);
if (ret == 0)
fh->is_streamed_on = 0;
return ret;
}
static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id i)
{
return 0;
}
/* ------------------------------------------------------------------
* File operations for the device
* ------------------------------------------------------------------
*/
/*extern void get_ppmgr_buf_info(char **start, unsigned int *size);*/
static int amlvideo_open(struct file *file)
{
struct vivi_dev *dev = video_drvdata(file);
struct vivi_fh *fh = NULL;
struct videobuf_res_privdata *res = NULL;
char *bstart = NULL;
unsigned int bsize = 0;
dev->vf = NULL;
dev->index = 0;
mutex_lock(&dev->mutex);
dev->users++;
if (dev->users > 1) {
dev->users--;
mutex_unlock(&dev->mutex);
return -EBUSY;
}
res = kzalloc(sizeof(*res), GFP_KERNEL);
if (!res || dev->res) {
kfree(res);
dev->users--;
mutex_unlock(&dev->mutex);
return -ENOMEM;
}
fh = kzalloc(sizeof(*fh), GFP_KERNEL);
if (!fh) {
kfree(res);
dev->users--;
mutex_unlock(&dev->mutex);
return -ENOMEM;
}
mutex_unlock(&dev->mutex);
file->private_data = fh;
fh->dev = dev;
fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fh->fmt = &formats[0];
fh->width = 1920;
fh->height = 1080;
res->priv = (void *)fh;
dev->res = res;
get_ppmgr_buf_info(&bstart, &bsize);
res->start = (resource_size_t)bstart;
res->end = (resource_size_t)(bstart + bsize - 1);
res->magic = MAGIC_RE_MEM;
videobuf_queue_res_init(&fh->vb_vidq, &vivi_video_qops, NULL,
&dev->slock, fh->type, V4L2_FIELD_INTERLACED,
sizeof(struct vivi_buffer), (void *)res, NULL);
AMLVIDEO_DBG("amlvideo open");
return 0;
}
static ssize_t amlvideo_read(struct file *file,
char __user *data, size_t count, loff_t *ppos)
{
struct vivi_fh *fh = file->private_data;
if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
return videobuf_read_stream(&fh->vb_vidq, data, count,
ppos, 0,
file->f_flags & O_NONBLOCK);
return 0;
}
static unsigned int amlvideo_poll(struct file *file,
struct poll_table_struct *wait)
{
struct vivi_fh *fh = file->private_data;
struct vivi_dev *dev = fh->dev;
dprintk(dev, 1, "%s\n", __func__);
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return POLLERR;
if (vf_peek(dev->vf_receiver_name))
return POLL_IN | POLLRDNORM;
poll_wait(file, &dev->wq, wait);
if (vf_peek(dev->vf_receiver_name))
return POLL_IN | POLLRDNORM;
else
return 0;
}
static int amlvideo_close(struct file *file)
{
struct vivi_fh *fh = file->private_data;
struct vivi_dev *dev = fh->dev;
videobuf_stop(&fh->vb_vidq);
videobuf_mmap_free(&fh->vb_vidq);
kfree(fh);
dev->index = 8;
/* if (dev->res) { */
kfree(dev->res);
dev->res = NULL;
/* } */
mutex_lock(&dev->mutex);
dev->users--;
mutex_unlock(&dev->mutex);
if (dev->inst == 0)
video_inuse = 0;
AMLVIDEO_DBG("amlvideo close");
return 0;
}
static int amlvideo_mmap(struct file *file, struct vm_area_struct *vma)
{
struct vivi_fh *fh = file->private_data;
struct vivi_dev *dev = fh->dev;
int ret;
dprintk(dev, 1, "mmap called, vma=0x%08lx\n", (unsigned long)vma);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
ret = videobuf_mmap_mapper(&fh->vb_vidq, vma);
dprintk(dev, 1, "vma start=0x%08lx, size=%ld, ret=%d\n",
(unsigned long)vma->vm_start,
(unsigned long)vma->vm_end - (unsigned long)vma->vm_start,
ret);
return ret;
}
static const struct v4l2_file_operations amlvideo_fops = {
.owner = THIS_MODULE,
.open = amlvideo_open,
.release = amlvideo_close,
.read = amlvideo_read,
.poll = amlvideo_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = amlvideo_mmap,
};
static const struct v4l2_ioctl_ops amlvideo_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_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
/*.vidioc_queryctrl = vidioc_queryctrl,
*.vidioc_g_ctrl = vidioc_g_ctrl,
*.vidioc_s_ctrl = vidioc_s_ctrl,
*/
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
.vidioc_g_parm = vidioc_g_parm,
};
static struct video_device amlvideo_template = {
.name = "amlvideo",
.fops = &amlvideo_fops,
.ioctl_ops = &amlvideo_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
/* .current_norm = V4L2_STD_NTSC_M , */
};
/* -----------------------------------------------------------------
* Initialization and module stuff
* -----------------------------------------------------------------
*/
static int amlvideo_release(void)
{
struct vivi_dev *dev;
struct list_head *list;
while (!list_empty(&vivi_devlist)) {
list = vivi_devlist.next;
list_del(list);
dev = list_entry(list, struct vivi_dev, vivi_devlist);
v4l2_info(&dev->v4l2_dev, "unregistering %s\n",
video_device_node_name(dev->vfd));
video_unregister_device(dev->vfd);
v4l2_device_unregister(&dev->v4l2_dev);
kfree(dev);
}
return 0;
}
static int __init amlvideo_create_instance(int inst)
{
struct vivi_dev *dev;
struct video_device *vfd;
int ret;
AMLVIDEO_INFO("%s called\n", __func__);
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name,
sizeof(dev->v4l2_dev.name),
"%s-%03d", AVMLVIDEO_MODULE_NAME, inst);
if (inst == n_devs - 1)
AMLVIDEO_INFO("v4l2_dev.name=:%s\n", dev->v4l2_dev.name);
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
if (ret)
goto free_dev;
/* init video dma queues */
init_waitqueue_head(&dev->wq);
INIT_LIST_HEAD(&dev->vidq.active);
init_waitqueue_head(&dev->vidq.wq);
/* initialize locks */
spin_lock_init(&dev->slock);
mutex_init(&dev->mutex);
mutex_init(&dev->vf_mutex);
ret = -ENOMEM;
vfd = video_device_alloc();
if (!vfd)
goto unreg_dev;
*vfd = amlvideo_template;
vfd->dev_debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
| V4L2_CAP_READWRITE;
dev->amlvideo_v4l_num = inst * 10 + video_nr_base;
if (inst == 0)
dev->amlvideo_v4l_num = video_nr_base;
else
dev->amlvideo_v4l_num = (inst - 1) + video_nr_base_second;
/* //////////////////////////////////////// */
/* vfd->v4l2_dev = &dev->v4l2_dev; */
/* //////////////////////////////////////// */
ret = video_register_device(vfd, VFL_TYPE_GRABBER,
dev->amlvideo_v4l_num);
if (ret < 0)
goto rel_vdev;
dev->inst = inst;
if (inst != 0) {
snprintf(dev->vf_receiver_name, AMLVIDEO_VF_NAME_SIZE,
RECEIVER_NAME_PIP ".%x", inst & 0xff);
snprintf(dev->vf_provider_name, AMLVIDEO_VF_NAME_SIZE,
PROVIDER_NAME_PIP ".%x", inst & 0xff);
} else {
memcpy(dev->vf_receiver_name, RECEIVER_NAME,
sizeof(RECEIVER_NAME));
memcpy(dev->vf_provider_name, PROVIDER_NAME,
sizeof(PROVIDER_NAME));
}
vf_receiver_init(&dev->video_vf_recv,
dev->vf_receiver_name,
&video_vf_receiver, dev);
vf_reg_receiver(&dev->video_vf_recv);
video_set_drvdata(vfd, dev);
/* Now that everything is fine, let's add it to device list */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
dev->vfd = vfd;
if (inst == n_devs - 1 || inst == 0)
v4l2_info(&dev->v4l2_dev,
"V4L2 device registered as %s\n",
video_device_node_name(vfd));
return 0;
rel_vdev: video_device_release(vfd);
unreg_dev: v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
dev->res = NULL;
kfree(dev);
return ret;
}
#undef NORM_MAXW
#undef NORM_MAXH
/* #define __init */
/* 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.
*/
int __init amlvideo_init(void)
{
int ret = 0, i;
AMLVIDEO_INFO("%s called", __func__);
if (n_devs <= 0)
n_devs = 1;
for (i = 0; i < n_devs; i++) {
ret = amlvideo_create_instance(i);
if (ret) {
/* If some instantiations succeeded,
* keep driver
*/
if (i)
ret = 0;
break;
}
}
if (ret < 0) {
/* printk(KERN_INFO "Error %d while
* loading vivi driver\n", ret);
*/
return ret;
}
/* printk(KERN_INFO "Video Technology Magazine Virtual Video " */
/* "Capture Board ver %u.%u.%u successfully loaded.\n", */
/* (AMLVIDEO_VERSION >> 16) & 0xFF, (AMLVIDEO_VERSION >> 8) & 0xFF, */
/* AMLVIDEO_VERSION & 0xFF); */
/* n_devs will reflect the actual number of allocated devices */
n_devs = i;
return ret;
}
void __exit amlvideo_exit(void)
{
/*vf_unreg_receiver(&video_vf_recv);*/
amlvideo_release();
}