blob: e11dfceaeaa5659316cbe438381f135818d6756e [file] [log] [blame]
/*
* drivers/amlogic/media_modules/frame_provider/decoder/yuv/vyuv.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.
*
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/videodev2.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/v4l2-ctrls.h>
#include <media/videobuf2-dma-contig.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/canvas/canvas.h>
#define VF_POOL_SIZE 4
#define MAX_WIDTH 1920U
#define MAX_HEIGHT 1088U
struct yuvdecoder_buffer {
struct vb2_buffer vb;
struct list_head list;
struct vframe_s vf;
};
struct yuvdecoder_dev {
struct platform_device *pdev;
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct vb2_queue queue;
struct mutex lock;
struct vframe_provider_s video_vf_prov;
bool streaming_on;
spinlock_t qlock;
struct list_head buf_list;
struct v4l2_format fmt;
};
#define DRIVER_NAME "amvdec_yuv"
#define MODULE_NAME "amvdec_yuv"
#define PROVIDER_NAME "decoder.yuv"
/* ------------------------------------------------------------------
* vframe_operations_s
* ------------------------------------------------------------------
*/
static struct vframe_s *yuv_vf_peek(void *);
static struct vframe_s *yuv_vf_get(void *);
static void yuv_vf_put(struct vframe_s *, void *);
static int yuv_vf_states(struct vframe_states *states, void *);
static int yuv_event_cb(int type, void *data, void *private_data);
static const struct vframe_operations_s yuv_vf_provider_ops = {
.peek = yuv_vf_peek,
.get = yuv_vf_get,
.put = yuv_vf_put,
.event_cb = yuv_event_cb,
.vf_states = yuv_vf_states,
};
static struct vframe_s *yuv_vf_peek(void *op_arg)
{
struct yuvdecoder_dev *dev = (struct yuvdecoder_dev *)op_arg;
struct yuvdecoder_buffer *buf;
unsigned long flags;
if (!dev->streaming_on)
return NULL;
spin_lock_irqsave(&dev->qlock, flags);
if (list_empty(&dev->buf_list)) {
spin_unlock_irqrestore(&dev->qlock, flags);
return NULL;
}
buf = list_first_entry(&dev->buf_list, struct yuvdecoder_buffer, list);
spin_unlock_irqrestore(&dev->qlock, flags);
return &buf->vf;
}
static struct vframe_s *yuv_vf_get(void *op_arg)
{
struct yuvdecoder_dev *dev = (struct yuvdecoder_dev *)op_arg;
struct yuvdecoder_buffer *buf;
unsigned long flags;
if (!dev->streaming_on)
return NULL;
spin_lock_irqsave(&dev->qlock, flags);
if (list_empty(&dev->buf_list)) {
spin_unlock_irqrestore(&dev->qlock, flags);
return NULL;
}
buf = list_first_entry(&dev->buf_list, struct yuvdecoder_buffer, list);
list_del(&buf->list);
spin_unlock_irqrestore(&dev->qlock, flags);
return &buf->vf;
}
static void yuv_vf_put(struct vframe_s *vf, void *op_arg)
{
struct yuvdecoder_dev *dev = (struct yuvdecoder_dev *)op_arg;
struct vb2_buffer *vb = (struct vb2_buffer *)vf->private_data;
if (!dev->streaming_on)
return;
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
}
static int yuv_event_cb(int type, void *data, void *private_data)
{
return 0;
}
static int yuv_vf_states(struct vframe_states *states, void *op_arg)
{
states->vf_pool_size = 0;
states->buf_free_num = 0;
states->buf_avail_num = 0;
states->buf_recycle_num = 0;
return 0;
}
/* ------------------------------------------------------------------
* vb2_ops
* ------------------------------------------------------------------
*/
static int queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vq);
if (vq->num_buffers + *nbuffers < VF_POOL_SIZE)
*nbuffers = VF_POOL_SIZE - vq->num_buffers;
if (*nplanes) {
struct v4l2_plane_pix_format *fmt =
dev->fmt.fmt.pix_mp.plane_fmt;
if ((*nplanes != 3) ||
(sizes[0] < fmt[0].sizeimage) ||
(sizes[1] < fmt[1].sizeimage) ||
(sizes[2] < fmt[2].sizeimage))
return -EINVAL;
else
return 0;
}
*nplanes = 3;
sizes[0] = dev->fmt.fmt.pix_mp.plane_fmt[0].sizeimage;
sizes[1] = dev->fmt.fmt.pix_mp.plane_fmt[1].sizeimage;
sizes[2] = dev->fmt.fmt.pix_mp.plane_fmt[2].sizeimage;
return 0;
}
static void buffer_queue(struct vb2_buffer *vb)
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct yuvdecoder_buffer *buf = container_of(vb,
struct yuvdecoder_buffer, vb);
unsigned long flags = 0;
spin_lock_irqsave(&dev->qlock, flags);
list_add_tail(&buf->list, &dev->buf_list);
spin_unlock_irqrestore(&dev->qlock, flags);
vf_notify_receiver(PROVIDER_NAME,
VFRAME_EVENT_PROVIDER_VFRAME_READY, NULL);
}
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vq);
dev->streaming_on = true;
vf_provider_init(&dev->video_vf_prov, PROVIDER_NAME,
&yuv_vf_provider_ops, dev);
vf_reg_provider(&dev->video_vf_prov);
vf_notify_receiver(PROVIDER_NAME, VFRAME_EVENT_PROVIDER_START, NULL);
return 0;
}
static void stop_streaming(struct vb2_queue *vq)
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vq);
unsigned long flags;
vf_unreg_provider(&dev->video_vf_prov);
dev->streaming_on = false;
spin_lock_irqsave(&dev->qlock, flags);
while (!list_empty(&dev->buf_list)) {
struct yuvdecoder_buffer *buf;
buf = list_entry(dev->buf_list.next, struct yuvdecoder_buffer,
list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&dev->qlock, flags);
}
static void yuvdecoder_lock(struct vb2_queue *vq)
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vq);
mutex_lock(&dev->lock);
}
static void yuvdecoder_unlock(struct vb2_queue *vq)
{
struct yuvdecoder_dev *dev = vb2_get_drv_priv(vq);
mutex_unlock(&dev->lock);
}
static const struct vb2_ops yuvdecoder_qops = {
.queue_setup = queue_setup,
.buf_queue = buffer_queue,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.wait_prepare = yuvdecoder_unlock,
.wait_finish = yuvdecoder_lock,
};
/* ------------------------------------------------------------------
* v4l2_file_operations
* ------------------------------------------------------------------
*/
static const struct v4l2_file_operations yuvdecoder_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vb2_fop_mmap,
};
/* ------------------------------------------------------------------
* v4l2_ioctl_ops
* ------------------------------------------------------------------
*/
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct yuvdecoder_dev *dev = video_drvdata(file);
strcpy(cap->driver, "yuvdecoder");
strcpy(cap->card, "yuvdecoder");
snprintf(cap->bus_info, sizeof(cap->bus_info),
"platform:%s", dev->v4l2_dev.name);
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_enum_fmt_vid_out_mplane(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (f->index >= 1)
return -EINVAL;
strlcpy(f->description, "YUV420P", sizeof(f->description));
f->pixelformat = V4L2_PIX_FMT_YUV420;
return 0;
}
static int vidioc_g_fmt_vid_out_mplane(struct file *file, void *priv,
struct v4l2_format *f)
{
struct yuvdecoder_dev *dev = video_drvdata(file);
if (dev->fmt.fmt.pix_mp.width == 0 || dev->fmt.fmt.pix_mp.height == 0)
return -EINVAL;
*f = dev->fmt;
return 0;
}
static int vidioc_try_fmt_vid_out_mplane(struct file *file, void *priv,
struct v4l2_format *f)
{
struct yuvdecoder_dev *dev = video_drvdata(file);
int i;
f->fmt.pix_mp.width = min(f->fmt.pix_mp.width, MAX_WIDTH);
f->fmt.pix_mp.height = min(f->fmt.pix_mp.height, MAX_HEIGHT);
f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420;
f->fmt.pix_mp.field = V4L2_FIELD_NONE;
f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709;
f->fmt.pix_mp.num_planes = 3;
for (i = 0; i < 3; i++) {
f->fmt.pix_mp.plane_fmt[i].sizeimage =
ALIGN(f->fmt.pix_mp.width, 64) *
f->fmt.pix_mp.height / ((i == 0) ? 1 : 4);
f->fmt.pix_mp.plane_fmt[i].bytesperline =
ALIGN(f->fmt.pix_mp.width, 64);
}
dev->fmt = *f;
return 0;
}
static int vidioc_s_fmt_vid_out_mplane(struct file *file, void *priv,
struct v4l2_format *f) {
struct yuvdecoder_dev *dev = video_drvdata(file);
struct vb2_queue *q = &dev->queue;
int ret;
if (vb2_is_busy(q))
return -EBUSY;
ret = vidioc_try_fmt_vid_out_mplane(file, priv, f);
if (ret < 0)
return ret;
return 0;
}
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
struct yuvdecoder_dev *dev = video_drvdata(file);
struct vb2_buffer *vb = dev->queue.bufs[p->index];
struct yuvdecoder_buffer *buf =
container_of(vb, struct yuvdecoder_buffer, vb);
struct vframe_s *vf = &buf->vf;
int ret;
if (vf->width == 0) {
unsigned long addr;
int canvas_id = 128 + 3 * p->index;
/* construct vframe */
vf->index = p->index;
vf->width = dev->fmt.fmt.pix_mp.width;
vf->bufWidth = dev->fmt.fmt.pix_mp.plane_fmt[0].bytesperline;
vf->height = dev->fmt.fmt.pix_mp.height;
vf->type = VIDTYPE_PROGRESSIVE | VIDTYPE_VIU_FIELD;
vf->type_original = vf->type;
vf->canvas0Addr = vf->canvas1Addr =
(((canvas_id + 2) & 0xff) << 16) |
(((canvas_id + 1) & 0xff) << 8) |
(canvas_id & 0xff);
vf->duration = 3000;
vf->duration_pulldown = 0;
vf->signal_type = 0;
vf->ratio_control = (0x100 * vf->height / vf->width) <<
DISP_RATIO_ASPECT_RATIO_BIT;
vf->orientation = 0;
vf->flag = 0;
vf->trans_fmt = 0;
vf->private_data = (void *)(dev->queue.bufs[p->index]);
vf->mem_handle = 0;
addr = vb2_dma_contig_plane_dma_addr(vb, 0);
canvas_config_ex(canvas_id, addr,
vf->bufWidth, vf->height,
CANVAS_ADDR_NOWRAP, CANVAS_BLKMODE_LINEAR, 7);
addr = vb2_dma_contig_plane_dma_addr(vb, 1);
canvas_config_ex(canvas_id + 1, addr,
vf->bufWidth / 2, vf->height / 2,
CANVAS_ADDR_NOWRAP, CANVAS_BLKMODE_LINEAR, 7);
addr = vb2_dma_contig_plane_dma_addr(vb, 2);
canvas_config_ex(canvas_id + 2, addr,
vf->bufWidth / 2, vf->height / 2,
CANVAS_ADDR_NOWRAP, CANVAS_BLKMODE_LINEAR, 7);
}
vf->pts = p->timestamp.tv_usec & 0xFFFFFFFF;
vf->ready_jiffies64 = jiffies_64;
ret = vb2_ioctl_qbuf(file, priv, p);
if (ret)
return ret;
return 0;
}
static int vidioc_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
const struct v4l2_frmsize_stepwise sizes = {
96, MAX_WIDTH, 16, 96, MAX_HEIGHT, 16 };
if (fsize->pixel_format != V4L2_PIX_FMT_YUV420)
return -EINVAL;
if (fsize->index > 0)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
fsize->stepwise = sizes;
return 0;
}
static const struct v4l2_ioctl_ops yuvdecoder_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out_mplane,
.vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_out_mplane,
.vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out_mplane,
.vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_out_mplane,
.vidioc_enum_framesizes = vidioc_enum_framesizes,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/* ------------------------------------------------------------------
* platform device
* ------------------------------------------------------------------
*/
static int amvdec_yuv_probe(struct platform_device *pdev)
{
return 0;
}
static int amvdec_yuv_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver amvdec_yuv_driver = {
.probe = amvdec_yuv_probe,
.remove = amvdec_yuv_remove,
.driver = {
.name = "amvdec_yuvdec",
}
};
static int amvdec_yuvdecoder_probe(struct platform_device *pdev)
{
struct yuvdecoder_dev *dev;
struct video_device *vdev;
struct vb2_queue *q;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(struct yuvdecoder_dev),
GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->pdev = pdev;
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret)
goto free_dev;
mutex_init(&dev->lock);
/* initialize the vb2 queue */
q = &dev->queue;
q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
q->io_modes = VB2_MMAP;
q->dev = &pdev->dev;
q->drv_priv = dev;
q->buf_struct_size = sizeof(struct yuvdecoder_buffer);
q->ops = &yuvdecoder_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->min_buffers_needed = 0;
q->lock = &dev->lock;
q->gfp_flags = GFP_DMA32;
ret = vb2_queue_init(q);
if (ret)
goto free_hdl;
INIT_LIST_HEAD(&dev->buf_list);
spin_lock_init(&dev->qlock);
/* initialize video_device */
vdev = &dev->vdev;
strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->fops = &yuvdecoder_fops;
vdev->ioctl_ops = &yuvdecoder_ioctl_ops;
vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE |
V4L2_CAP_STREAMING;
vdev->lock = &dev->lock;
vdev->queue = q;
vdev->v4l2_dev = &dev->v4l2_dev;
vdev->vfl_dir = VFL_DIR_TX;
video_set_drvdata(vdev, dev);
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
if (ret < 0)
goto free_hdl;
dev_info(&pdev->dev, "yubdecoder V4L2 device registered\n");
return 0;
free_hdl:
v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
return ret;
}
static int amvdec_yuvdecoder_remove(struct platform_device *pdev)
{
struct yuvdecoder_dev *dev = platform_get_drvdata(pdev);
video_unregister_device(&dev->vdev);
v4l2_device_unregister(&dev->v4l2_dev);
return 0;
}
static const struct of_device_id amvdec_yuvdecoder_of_match[] = {
{ .compatible = "amlogic, amvdec_yuv" },
{}
};
static struct platform_driver amvdec_yuvdecoder_driver = {
.probe = amvdec_yuvdecoder_probe,
.remove = amvdec_yuvdecoder_remove,
.driver = {
.owner = THIS_MODULE,
.name = "yuvdecoder",
.of_match_table = amvdec_yuvdecoder_of_match,
},
};
static int __init amvdec_yuvdecoder_driver_init(void)
{
int r;
r = platform_driver_register(&(amvdec_yuv_driver));
if (r)
return r;
r = platform_driver_register(&(amvdec_yuvdecoder_driver));
if (r)
return r;
return 0;
}
static void __exit amvdec_yuvdecoder_driver_exit(void)
{
platform_driver_unregister(&(amvdec_yuv_driver));
platform_driver_unregister(&(amvdec_yuvdecoder_driver));
}
module_init(amvdec_yuvdecoder_driver_init);
module_exit(amvdec_yuvdecoder_driver_exit);
MODULE_DEVICE_TABLE(of, amvdec_yuvdecoder_of_match);
MODULE_DESCRIPTION("AMLOGIC YUV Video Decoder Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tim Yao <tim.yao@amlogic.com>");