From: Alpha Lin Date: Fri, 14 Nov 2014 03:41:10 +0000 (+0900) Subject: CHROMIUM: [media] Add rk3288-vpu driver (vp8-encoder only) X-Git-Tag: firefly_0821_release~2320 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=c25878fcf82bc41dc7f7e20bff2118a1fa51947e;p=firefly-linux-kernel-4.4.55.git CHROMIUM: [media] Add rk3288-vpu driver (vp8-encoder only) Currently it consists of implementations of a platform driver and a V4L2 mem-to-mem encoder device. Only VP8 encoding is implemented currently. BUG=chrome-os-partner:33728 TEST=video_encode_acceleator_unittest Signed-off-by: Alpha Lin Signed-off-by: Herman Chen Signed-off-by: Jeffy Chen Signed-off-by: Tomasz Figa Reviewed-on: https://chromium-review.googlesource.com/237614 Reviewed-by: Pawel Osciak Conflicts: drivers/media/platform/Makefile Change-Id: I6e2c44ff378c68af4f1db071c7909a0870d9171a Signed-off-by: Jeffy Chen Signed-off-by: Yakir Yang --- diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 7647a836072f..3a006fa02e6e 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -267,6 +267,17 @@ config VIDEO_TI_VPE Support for the TI VPE(Video Processing Engine) block found on DRA7XX SoC. +config VIDEO_RK3288_VPU + tristate "Rockchip RK3288 VPU driver" + depends on VIDEO_DEV && VIDEO_V4L2 + select VIDEOBUF2_DMA_CONTIG + default n + ---help--- + Support for the VPU video codec found on Rockchip RK3288 SoC. + + To compile this driver as a module, choose M here: the module + will be called rk3288-vpu. + config VIDEO_TI_VPE_DEBUG bool "VPE debug messages" depends on VIDEO_TI_VPE diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index efa0295af87b..63d3e79c9b49 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -54,4 +54,6 @@ obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x/ obj-$(CONFIG_VIDEO_XILINX) += xilinx/ +obj-$(CONFIG_VIDEO_RK3288_VPU) += rk3288-vpu/ + ccflags-y += -I$(srctree)/drivers/media/i2c diff --git a/drivers/media/platform/rk3288-vpu/Makefile b/drivers/media/platform/rk3288-vpu/Makefile new file mode 100644 index 000000000000..3eb058bc5fde --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/Makefile @@ -0,0 +1,7 @@ + +obj-$(CONFIG_VIDEO_RK3288_VPU) += rk3288-vpu.o + +rk3288-vpu-y += rk3288_vpu.o \ + rk3288_vpu_enc.o \ + rk3288_vpu_hw.o \ + rk3288_vpu_hw_vp8e.o diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu.c b/drivers/media/platform/rk3288-vpu/rk3288_vpu.c new file mode 100644 index 000000000000..a217a8facce4 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu.c @@ -0,0 +1,683 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd. + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "rk3288_vpu_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rk3288_vpu_enc.h" +#include "rk3288_vpu_hw.h" + +int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, + "Debug level - higher value produces more verbose messages"); + +/* + * DMA coherent helpers. + */ + +int rk3288_vpu_aux_buf_alloc(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_aux_buf *buf, size_t size) +{ + buf->cpu = dma_alloc_coherent(vpu->dev, size, &buf->dma, GFP_KERNEL); + if (!buf->cpu) + return -ENOMEM; + + buf->size = size; + return 0; +} + +void rk3288_vpu_aux_buf_free(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_aux_buf *buf) +{ + dma_free_coherent(vpu->dev, buf->size, buf->cpu, buf->dma); + + buf->cpu = NULL; + buf->dma = 0; + buf->size = 0; +} + +/* + * Context scheduling. + */ + +static void rk3288_vpu_prepare_run(struct rk3288_vpu_ctx *ctx) +{ + if (ctx->run_ops->prepare_run) + ctx->run_ops->prepare_run(ctx); +} + +static void __rk3288_vpu_dequeue_run_locked(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_buf *src, *dst; + + /* + * Since ctx was dequeued from ready_ctxs list, we know that it has + * at least one buffer in each queue. + */ + src = list_first_entry(&ctx->src_queue, struct rk3288_vpu_buf, list); + dst = list_first_entry(&ctx->dst_queue, struct rk3288_vpu_buf, list); + + list_del(&src->list); + list_del(&dst->list); + + ctx->run.src = src; + ctx->run.dst = dst; +} + +static void rk3288_vpu_try_run(struct rk3288_vpu_dev *dev) +{ + struct rk3288_vpu_ctx *ctx = NULL; + unsigned long flags; + + vpu_debug_enter(); + + spin_lock_irqsave(&dev->irqlock, flags); + + if (list_empty(&dev->ready_ctxs)) + /* Nothing to do. */ + goto out; + + if (test_and_set_bit(VPU_RUNNING, &dev->state)) + /* + * The hardware is already running. We will pick another + * run after we get the notification in rk3288_vpu_run_done(). + */ + goto out; + + ctx = list_entry(dev->ready_ctxs.next, struct rk3288_vpu_ctx, list); + list_del_init(&ctx->list); + + dev->current_ctx = ctx; + __rk3288_vpu_dequeue_run_locked(ctx); + +out: + spin_unlock_irqrestore(&dev->irqlock, flags); + + if (ctx) { + rk3288_vpu_prepare_run(ctx); + rk3288_vpu_run(ctx); + } + + vpu_debug_leave(); +} + +static void __rk3288_vpu_try_context_locked(struct rk3288_vpu_dev *dev, + struct rk3288_vpu_ctx *ctx) +{ + if (!list_empty(&ctx->list)) + /* Context already queued. */ + return; + + if (!list_empty(&ctx->dst_queue) && !list_empty(&ctx->src_queue)) + list_add_tail(&ctx->list, &dev->ready_ctxs); +} + +void rk3288_vpu_run_done(struct rk3288_vpu_ctx *ctx, + enum vb2_buffer_state result) +{ + struct vb2_buffer *src = &ctx->run.src->b; + struct vb2_buffer *dst = &ctx->run.dst->b; + struct rk3288_vpu_dev *dev = ctx->dev; + unsigned long flags; + + vpu_debug_enter(); + + if (ctx->run_ops->run_done) + ctx->run_ops->run_done(ctx, result); + + dst->v4l2_buf.timestamp = src->v4l2_buf.timestamp; + vb2_buffer_done(&ctx->run.src->b, result); + vb2_buffer_done(&ctx->run.dst->b, result); + + dev->current_ctx = NULL; + wake_up_all(&dev->run_wq); + + spin_lock_irqsave(&dev->irqlock, flags); + + __rk3288_vpu_try_context_locked(dev, ctx); + clear_bit(VPU_RUNNING, &dev->state); + + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* Try scheduling another run to see if we have anything left to do. */ + rk3288_vpu_try_run(dev); + + vpu_debug_leave(); +} + +void rk3288_vpu_try_context(struct rk3288_vpu_dev *dev, + struct rk3288_vpu_ctx *ctx) +{ + unsigned long flags; + + vpu_debug_enter(); + + spin_lock_irqsave(&dev->irqlock, flags); + + __rk3288_vpu_try_context_locked(dev, ctx); + + spin_unlock_irqrestore(&dev->irqlock, flags); + + rk3288_vpu_try_run(dev); + + vpu_debug_enter(); +} + +/* + * Control registration. + */ + +#define IS_VPU_PRIV(x) ((V4L2_CTRL_ID2CLASS(x) == V4L2_CTRL_CLASS_MPEG) && \ + V4L2_CTRL_DRIVER_PRIV(x)) + +int rk3288_vpu_ctrls_setup(struct rk3288_vpu_ctx *ctx, + const struct v4l2_ctrl_ops *ctrl_ops, + struct rk3288_vpu_control *controls, + unsigned num_ctrls, + const char *const *(*get_menu)(u32)) +{ + struct v4l2_ctrl_config cfg; + int i; + + if (num_ctrls > ARRAY_SIZE(ctx->ctrls)) { + vpu_err("context control array not large enough\n"); + return -ENOSPC; + } + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, num_ctrls); + if (ctx->ctrl_handler.error) { + vpu_err("v4l2_ctrl_handler_init failed\n"); + return ctx->ctrl_handler.error; + } + + for (i = 0; i < num_ctrls; i++) { + if (IS_VPU_PRIV(controls[i].id) + || controls[i].id >= V4L2_CID_CUSTOM_BASE + || controls[i].type == V4L2_CTRL_TYPE_PRIVATE) { + memset(&cfg, 0, sizeof(struct v4l2_ctrl_config)); + + cfg.ops = ctrl_ops; + cfg.id = controls[i].id; + cfg.min = controls[i].minimum; + cfg.max = controls[i].maximum; + cfg.max_stores = controls[i].max_stores; + cfg.def = controls[i].default_value; + cfg.name = controls[i].name; + cfg.type = controls[i].type; + cfg.elem_size = controls[i].elem_size; + memcpy(cfg.dims, controls[i].dims, sizeof(cfg.dims)); + + if (cfg.type == V4L2_CTRL_TYPE_MENU) { + cfg.menu_skip_mask = cfg.menu_skip_mask; + cfg.qmenu = get_menu(cfg.id); + } else { + cfg.step = controls[i].step; + } + + ctx->ctrls[i] = v4l2_ctrl_new_custom( + &ctx->ctrl_handler, &cfg, NULL); + } else { + if (controls[i].type == V4L2_CTRL_TYPE_MENU) { + ctx->ctrls[i] = + v4l2_ctrl_new_std_menu + (&ctx->ctrl_handler, + ctrl_ops, + controls[i].id, + controls[i].maximum, + 0, + controls[i]. + default_value); + } else { + ctx->ctrls[i] = + v4l2_ctrl_new_std(&ctx->ctrl_handler, + ctrl_ops, + controls[i].id, + controls[i].minimum, + controls[i].maximum, + controls[i].step, + controls[i]. + default_value); + } + } + + if (ctx->ctrl_handler.error) { + vpu_err("Adding control (%d) failed\n", i); + return ctx->ctrl_handler.error; + } + + if (controls[i].is_volatile && ctx->ctrls[i]) + ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_VOLATILE; + if (controls[i].is_read_only && ctx->ctrls[i]) + ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_READ_ONLY; + if (controls[i].can_store && ctx->ctrls[i]) + ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_CAN_STORE; + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + ctx->num_ctrls = num_ctrls; + return 0; +} + +void rk3288_vpu_ctrls_delete(struct rk3288_vpu_ctx *ctx) +{ + int i; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + for (i = 0; i < ctx->num_ctrls; i++) + ctx->ctrls[i] = NULL; +} + +/* + * V4L2 file operations. + */ + +static int rk3288_vpu_open(struct file *filp) +{ + struct video_device *vdev = video_devdata(filp); + struct rk3288_vpu_dev *dev = video_drvdata(filp); + struct rk3288_vpu_ctx *ctx = NULL; + struct vb2_queue *q; + int ret = 0; + + /* + * We do not need any extra locking here, because we operate only + * on local data here, except reading few fields from dev, which + * do not change through device's lifetime (which is guaranteed by + * reference on module from open()) and V4L2 internal objects (such + * as vdev and ctx->fh), which have proper locking done in respective + * helper functions used here. + */ + + vpu_debug_enter(); + + /* Allocate memory for context */ + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err_leave; + } + + v4l2_fh_init(&ctx->fh, video_devdata(filp)); + filp->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + ctx->dev = dev; + INIT_LIST_HEAD(&ctx->src_queue); + INIT_LIST_HEAD(&ctx->dst_queue); + INIT_LIST_HEAD(&ctx->list); + + if (vdev == dev->vfd_enc) { + /* only for encoder */ + ret = rk3288_vpu_enc_init(ctx); + if (ret) { + vpu_err("Failed to initialize encoder context\n"); + goto err_fh_free; + } + } else { + ret = -ENOENT; + goto err_fh_free; + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + + /* Init videobuf2 queue for CAPTURE */ + q = &ctx->vq_dst; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->drv_priv = &ctx->fh; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->lock = &dev->vpu_mutex; + q->buf_struct_size = sizeof(struct rk3288_vpu_buf); + + if (vdev == dev->vfd_enc) + q->ops = get_enc_queue_ops(); + + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + ret = vb2_queue_init(q); + if (ret) { + vpu_err("Failed to initialize videobuf2 queue(capture)\n"); + goto err_enc_dec_exit; + } + + /* Init videobuf2 queue for OUTPUT */ + q = &ctx->vq_src; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + q->drv_priv = &ctx->fh; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->lock = &dev->vpu_mutex; + q->buf_struct_size = sizeof(struct rk3288_vpu_buf); + + if (vdev == dev->vfd_enc) + q->ops = get_enc_queue_ops(); + + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + ret = vb2_queue_init(q); + if (ret) { + vpu_err("Failed to initialize videobuf2 queue(output)\n"); + goto err_vq_dst_release; + } + + vpu_debug_leave(); + + return 0; + +err_vq_dst_release: + vb2_queue_release(&ctx->vq_dst); +err_enc_dec_exit: + if (vdev == dev->vfd_enc) + rk3288_vpu_enc_exit(ctx); +err_fh_free: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +err_leave: + vpu_debug_leave(); + + return ret; +} + +static int rk3288_vpu_release(struct file *filp) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(filp->private_data); + struct video_device *vdev = video_devdata(filp); + struct rk3288_vpu_dev *dev = ctx->dev; + + /* + * No need for extra locking because this was the last reference + * to this file. + */ + + vpu_debug_enter(); + + /* + * vb2_queue_release() ensures that streaming is stopped, which + * in turn means that there are no frames still being processed + * by hardware. + */ + vb2_queue_release(&ctx->vq_src); + vb2_queue_release(&ctx->vq_dst); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (vdev == dev->vfd_enc) + rk3288_vpu_enc_exit(ctx); + + kfree(ctx); + + vpu_debug_leave(); + + return 0; +} + +static unsigned int rk3288_vpu_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(filp->private_data); + struct vb2_queue *src_q, *dst_q; + struct vb2_buffer *src_vb = NULL, *dst_vb = NULL; + unsigned int rc = 0; + unsigned long flags; + + vpu_debug_enter(); + + src_q = &ctx->vq_src; + dst_q = &ctx->vq_dst; + + /* + * There has to be at least one buffer queued on each queued_list, which + * means either in driver already or waiting for driver to claim it + * and start processing. + */ + if ((!vb2_is_streaming(src_q) || list_empty(&src_q->queued_list)) && + (!vb2_is_streaming(dst_q) || list_empty(&dst_q->queued_list))) { + vpu_debug(0, "src q streaming %d, dst q streaming %d, src list empty(%d), dst list empty(%d)\n", + src_q->streaming, dst_q->streaming, + list_empty(&src_q->queued_list), + list_empty(&dst_q->queued_list)); + return POLLERR; + } + + poll_wait(filp, &ctx->fh.wait, wait); + poll_wait(filp, &src_q->done_wq, wait); + poll_wait(filp, &dst_q->done_wq, wait); + + if (v4l2_event_pending(&ctx->fh)) + rc |= POLLPRI; + + spin_lock_irqsave(&src_q->done_lock, flags); + + if (!list_empty(&src_q->done_list)) + src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer, + done_entry); + + if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE || + src_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLOUT | POLLWRNORM; + + spin_unlock_irqrestore(&src_q->done_lock, flags); + + spin_lock_irqsave(&dst_q->done_lock, flags); + + if (!list_empty(&dst_q->done_list)) + dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer, + done_entry); + + if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE || + dst_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLIN | POLLRDNORM; + + spin_unlock_irqrestore(&dst_q->done_lock, flags); + + return rc; +} + +static int rk3288_vpu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(filp->private_data); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int ret; + + vpu_debug_enter(); + + if (offset < DST_QUEUE_OFF_BASE) { + vpu_debug(4, "mmaping source\n"); + + ret = vb2_mmap(&ctx->vq_src, vma); + } else { /* capture */ + vpu_debug(4, "mmaping destination\n"); + + vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); + ret = vb2_mmap(&ctx->vq_dst, vma); + } + + vpu_debug_leave(); + + return ret; +} + +static const struct v4l2_file_operations rk3288_vpu_fops = { + .owner = THIS_MODULE, + .open = rk3288_vpu_open, + .release = rk3288_vpu_release, + .poll = rk3288_vpu_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = rk3288_vpu_mmap, +}; + +/* + * Platform driver. + */ + +static int rk3288_vpu_probe(struct platform_device *pdev) +{ + struct rk3288_vpu_dev *vpu = NULL; + struct video_device *vfd; + int ret = 0; + + vpu_debug_enter(); + + vpu = devm_kzalloc(&pdev->dev, sizeof(*vpu), GFP_KERNEL); + if (!vpu) + return -ENOMEM; + + vpu->dev = &pdev->dev; + vpu->pdev = pdev; + mutex_init(&vpu->vpu_mutex); + spin_lock_init(&vpu->irqlock); + INIT_LIST_HEAD(&vpu->ready_ctxs); + init_waitqueue_head(&vpu->run_wq); + + ret = rk3288_vpu_hw_probe(vpu); + if (ret) { + dev_err(&pdev->dev, "vcodec_hw_probe failed\n"); + goto err_hw_probe; + } + + vpu->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(vpu->alloc_ctx)) { + ret = PTR_ERR(vpu->alloc_ctx); + goto err_dma_contig; + } + + ret = v4l2_device_register(&pdev->dev, &vpu->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + goto err_v4l2_dev_reg; + } + + platform_set_drvdata(pdev, vpu); + + /* encoder */ + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&vpu->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_enc_alloc; + } + + vfd->fops = &rk3288_vpu_fops; + vfd->ioctl_ops = get_enc_v4l2_ioctl_ops(); + vfd->release = video_device_release; + vfd->lock = &vpu->vpu_mutex; + vfd->v4l2_dev = &vpu->v4l2_dev; + vfd->vfl_dir = VFL_DIR_M2M; + snprintf(vfd->name, sizeof(vfd->name), "%s", RK3288_VPU_ENC_NAME); + vpu->vfd_enc = vfd; + + video_set_drvdata(vfd, vpu); + + ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); + if (ret) { + v4l2_err(&vpu->v4l2_dev, "Failed to register video device\n"); + video_device_release(vfd); + goto err_enc_reg; + } + + v4l2_info(&vpu->v4l2_dev, + "Rockchip RK3288 VPU encoder registered as /vpu/video%d\n", + vfd->num); + + vpu_debug_leave(); + + return 0; + +err_enc_reg: + video_device_release(vpu->vfd_enc); +err_enc_alloc: + v4l2_device_unregister(&vpu->v4l2_dev); +err_v4l2_dev_reg: + vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx); +err_dma_contig: + rk3288_vpu_hw_remove(vpu); +err_hw_probe: + pr_debug("%s-- with error\n", __func__); + vpu_debug_leave(); + + return ret; +} + +static int rk3288_vpu_remove(struct platform_device *pdev) +{ + struct rk3288_vpu_dev *vpu = platform_get_drvdata(pdev); + + vpu_debug_enter(); + + v4l2_info(&vpu->v4l2_dev, "Removing %s\n", pdev->name); + + /* + * We are safe here assuming that .remove() got called as + * a result of module removal, which guarantees that all + * contexts have been released. + */ + + video_unregister_device(vpu->vfd_enc); + v4l2_device_unregister(&vpu->v4l2_dev); + vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx); + rk3288_vpu_hw_remove(vpu); + + vpu_debug_leave(); + + return 0; +} + +static struct platform_device_id vpu_driver_ids[] = { + { .name = "rk3288-vpu", }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(platform, vpu_driver_ids); + +#ifdef CONFIG_OF +static const struct of_device_id of_rk3288_vpu_match[] = { + { .compatible = "rockchip,rk3288-vpu", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_rk3288_vpu_match); +#endif + +static struct platform_driver rk3288_vpu_driver = { + .probe = rk3288_vpu_probe, + .remove = rk3288_vpu_remove, + .id_table = vpu_driver_ids, + .driver = { + .name = RK3288_VPU_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_rk3288_vpu_match), + }, +}; +module_platform_driver(rk3288_vpu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Alpha Lin "); +MODULE_AUTHOR("Tomasz Figa "); +MODULE_DESCRIPTION("Rockchip RK3288 VPU codec driver"); diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_common.h b/drivers/media/platform/rk3288-vpu/rk3288_vpu_common.h new file mode 100644 index 000000000000..d4d58ff46ad1 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_common.h @@ -0,0 +1,436 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd. + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef RK3288_VPU_COMMON_H_ +#define RK3288_VPU_COMMON_H_ + +/* Enable debugging by default for now. */ +#define DEBUG + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rk3288_vpu_hw.h" + +#define RK3288_VPU_NAME "rk3288-vpu" +#define RK3288_VPU_DEC_NAME "rk3288-vpu-dec" +#define RK3288_VPU_ENC_NAME "rk3288-vpu-enc" + +#define V4L2_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0x1000) + +#define DST_QUEUE_OFF_BASE (TASK_SIZE / 2) + +#define RK3288_VPU_MAX_CTRLS 32 + +#define MB_DIM 16 +#define MB_WIDTH(x_size) DIV_ROUND_UP(x_size, MB_DIM) +#define MB_HEIGHT(y_size) DIV_ROUND_UP(y_size, MB_DIM) + +struct rk3288_vpu_variant; +struct rk3288_vpu_ctx; +struct rk3288_vpu_codec_ops; + +/** + * enum rk3288_vpu_codec_mode - codec operating mode. + * @RK_VPU_CODEC_NONE: No operating mode. Used for RAW video formats. + * @RK_VPU_CODEC_H264D: H264 decoder. + * @RK_VPU_CODEC_VP8D: VP8 decoder. + * @RK_VPU_CODEC_H264E: H264 encoder. + * @RK_VPU_CODEC_VP8E: VP8 encoder. + */ +enum rk3288_vpu_codec_mode { + RK_VPU_CODEC_NONE = -1, + RK_VPU_CODEC_H264D, + RK_VPU_CODEC_VP8D, + RK_VPU_CODEC_H264E, + RK_VPU_CODEC_VP8E +}; + +/** + * enum rk3288_vpu_plane - indices of planes inside a VB2 buffer. + * @PLANE_Y: Plane containing luminance data (also denoted as Y). + * @PLANE_CB_CR: Plane containing interleaved chrominance data (also + * denoted as CbCr). + * @PLANE_CB: Plane containing CB part of chrominance data. + * @PLANE_CR: Plane containing CR part of chrominance data. + */ +enum rk3288_vpu_plane { + PLANE_Y = 0, + PLANE_CB_CR = 1, + PLANE_CB = 1, + PLANE_CR = 2, +}; + +/** + * struct rk3288_vpu_vp8e_buf_data - mode-specific per-buffer data + * @dct_offset: Offset inside the buffer to DCT partition. + * @hdr_size: Size of header data in the buffer. + * @ext_hdr_size: Size of ext header data in the buffer. + * @dct_size: Size of DCT partition in the buffer. + * @header: Frame header to copy to destination buffer. + */ +struct rk3288_vpu_vp8e_buf_data { + size_t dct_offset; + size_t hdr_size; + size_t ext_hdr_size; + size_t dct_size; + u8 header[RK3288_HEADER_SIZE]; +}; + +/** + * struct rk3288_vpu_buf - Private data related to each VB2 buffer. + * @list: List head for queuing in buffer queue. + * @b: Pointer to related VB2 buffer. + * @flags: Buffer state. See enum rk3288_vpu_buf_flags. + */ +struct rk3288_vpu_buf { + struct vb2_buffer b; + struct list_head list; + + /* Mode-specific data. */ + union { + struct rk3288_vpu_vp8e_buf_data vp8e; + }; +}; + +/** + * enum rk3288_vpu_state - bitwise flags indicating hardware state. + * @VPU_RUNNING: The hardware has been programmed for operation + * and is running at the moment. + */ +enum rk3288_vpu_state { + VPU_RUNNING = BIT(0), +}; + +/** + * struct rk3288_vpu_dev - driver data + * @v4l2_dev: V4L2 device to register video devices for. + * @vfd_dec: Video device for decoder. + * @vfd_enc: Video device for encoder. + * @pdev: Pointer to VPU platform device. + * @dev: Pointer to device for convenient logging using + * dev_ macros. + * @alloc_ctx: VB2 allocator context. + * @aclk_vcodec: Handle of ACLK clock. + * @hclk_vcodec: Handle of HCLK clock. + * @base: Mapped address of VPU registers. + * @enc_base: Mapped address of VPU encoder register for convenience. + * @dec_base: Mapped address of VPU decoder register for convenience. + * @mapping: DMA IOMMU mapping. + * @vpu_mutex: Mutex to synchronize V4L2 calls. + * @irqlock: Spinlock to synchronize access to data structures + * shared with interrupt handlers. + * @state: Device state. + * @ready_ctxs: List of contexts ready to run. + * @variant: Hardware variant-specfic parameters. + * @current_ctx: Context being currently processed by hardware. + * @run_wq: Wait queue to wait for run completion. + * @watchdog_work: Delayed work for hardware timeout handling. + */ +struct rk3288_vpu_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd_dec; + struct video_device *vfd_enc; + struct platform_device *pdev; + struct device *dev; + void *alloc_ctx; + struct clk *aclk_vcodec; + struct clk *hclk_vcodec; + void __iomem *base; + void __iomem *enc_base; + void __iomem *dec_base; + struct dma_iommu_mapping *mapping; + + struct mutex vpu_mutex; /* video_device lock */ + spinlock_t irqlock; + unsigned long state; + struct list_head ready_ctxs; + const struct rk3288_vpu_variant *variant; + struct rk3288_vpu_ctx *current_ctx; + wait_queue_head_t run_wq; + struct delayed_work watchdog_work; +}; + +/** + * struct rk3288_vpu_run_ops - per context operations on run data. + * @prepare_run: Called when the context was selected for running + * to prepare operating mode specific data. + * @run_done: Called when hardware completed the run to collect + * operating mode specific data from hardware and + * finalize the processing. + */ +struct rk3288_vpu_run_ops { + void (*prepare_run)(struct rk3288_vpu_ctx *); + void (*run_done)(struct rk3288_vpu_ctx *, enum vb2_buffer_state); +}; + +/** + * struct rk3288_vpu_vp8e_run - per-run data specific to VP8 encoding. + * @reg_params: Pointer to a buffer containing register values prepared + * by user space. + */ +struct rk3288_vpu_vp8e_run { + const struct rk3288_vp8e_reg_params *reg_params; +}; + +/** + * struct rk3288_vpu_run - per-run data for hardware code. + * @src: Source buffer to be processed. + * @dst: Destination buffer to be processed. + * @priv_src: Hardware private source buffer. + * @priv_dst: Hardware private destination buffer. + */ +struct rk3288_vpu_run { + /* Generic for more than one operating mode. */ + struct rk3288_vpu_buf *src; + struct rk3288_vpu_buf *dst; + + struct rk3288_vpu_aux_buf priv_src; + struct rk3288_vpu_aux_buf priv_dst; + + /* Specific for particular operating modes. */ + union { + struct rk3288_vpu_vp8e_run vp8e; + /* Other modes will need different data. */ + }; +}; + +/** + * struct rk3288_vpu_ctx - Context (instance) private data. + * + * @dev: VPU driver data to which the context belongs. + * @fh: V4L2 file handler. + * + * @vpu_src_fmt: Descriptor of active source format. + * @src_fmt: V4L2 pixel format of active source format. + * @vpu_dst_fmt: Descriptor of active destination format. + * @dst_fmt: V4L2 pixel format of active destination format. + * + * @vq_src: Videobuf2 source queue. + * @src_queue: Internal source buffer queue. + * @src_crop: Configured source crop rectangle (encoder-only). + * @vq_dst: Videobuf2 destination queue + * @dst_queue: Internal destination buffer queue. + * + * @ctrls: Array containing pointer to registered controls. + * @ctrl_handler: Control handler used to register controls. + * @num_ctrls: Number of registered controls. + * + * @list: List head for queue of ready contexts. + * + * @run: Structure containing data about currently scheduled + * processing run. + * @run_ops: Set of operations related to currently scheduled run. + * @hw: Structure containing hardware-related context. + */ +struct rk3288_vpu_ctx { + struct rk3288_vpu_dev *dev; + struct v4l2_fh fh; + + /* Format info */ + struct rk3288_vpu_fmt *vpu_src_fmt; + struct v4l2_pix_format_mplane src_fmt; + struct rk3288_vpu_fmt *vpu_dst_fmt; + struct v4l2_pix_format_mplane dst_fmt; + + /* VB2 queue data */ + struct vb2_queue vq_src; + struct list_head src_queue; + struct v4l2_rect src_crop; + struct vb2_queue vq_dst; + struct list_head dst_queue; + + /* Controls */ + struct v4l2_ctrl *ctrls[RK3288_VPU_MAX_CTRLS]; + struct v4l2_ctrl_handler ctrl_handler; + unsigned num_ctrls; + + /* Various runtime data */ + struct list_head list; + + struct rk3288_vpu_run run; + const struct rk3288_vpu_run_ops *run_ops; + struct rk3288_vpu_hw_ctx hw; +}; + +/** + * struct rk3288_vpu_fmt - information about supported video formats. + * @name: Human readable name of the format. + * @fourcc: FourCC code of the format. See V4L2_PIX_FMT_*. + * @codec_mode: Codec mode related to this format. See + * enum rk3288_vpu_codec_mode. + * @num_planes: Number of planes used by this format. + * @depth: Depth of each plane in bits per pixel. + * @enc_fmt: Format identifier for encoder registers. + */ +struct rk3288_vpu_fmt { + char *name; + u32 fourcc; + enum rk3288_vpu_codec_mode codec_mode; + int num_planes; + u8 depth[VIDEO_MAX_PLANES]; + enum rk3288_vpu_enc_fmt enc_fmt; +}; + +/** + * struct rk3288_vpu_control - information about controls to be registered. + * @id: Control ID. + * @type: Type of the control. + * @name: Human readable name of the control. + * @minimum: Minimum value of the control. + * @maximum: Maximum value of the control. + * @step: Control value increase step. + * @menu_skip_mask: Mask of invalid menu positions. + * @default_value: Initial value of the control. + * @max_stores: Maximum number of configration stores. + * @dims: Size of each dimension of compound control. + * @elem_size: Size of individual element of compound control. + * @is_volatile: Control is volatile. + * @is_read_only: Control is read-only. + * @can_store: Control uses configuration stores. + * + * See also struct v4l2_ctrl_config. + */ +struct rk3288_vpu_control { + u32 id; + + enum v4l2_ctrl_type type; + const char *name; + s32 minimum; + s32 maximum; + s32 step; + u32 menu_skip_mask; + s32 default_value; + s32 max_stores; + u32 dims[V4L2_CTRL_MAX_DIMS]; + u32 elem_size; + + bool is_volatile:1; + bool is_read_only:1; + bool can_store:1; +}; + +/* Logging helpers */ + +/** + * debug - Module parameter to control level of debugging messages. + * + * Level of debugging messages can be controlled by bits of module parameter + * called "debug". Meaning of particular bits is as follows: + * + * bit 0 - global information: mode, size, init, release + * bit 1 - each run start/result information + * bit 2 - contents of small controls from userspace + * bit 3 - contents of big controls from userspace + * bit 4 - detail fmt, ctrl, buffer q/dq information + * bit 5 - detail function enter/leave trace information + * bit 6 - register write/read information + */ +extern int debug; + +#define vpu_debug(level, fmt, args...) \ + do { \ + if (debug & BIT(level)) \ + pr_debug("%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } while (0) + +#define vpu_debug_enter() vpu_debug(5, "enter\n") +#define vpu_debug_leave() vpu_debug(5, "leave\n") + +#define vpu_err(fmt, args...) \ + pr_err("%s:%d: " fmt, __func__, __LINE__, ##args) + +static inline char *fmt2str(u32 fmt, char *str) +{ + char a = fmt & 0xFF; + char b = (fmt >> 8) & 0xFF; + char c = (fmt >> 16) & 0xFF; + char d = (fmt >> 24) & 0xFF; + + sprintf(str, "%c%c%c%c", a, b, c, d); + + return str; +} + +/* Structure access helpers. */ +static inline struct rk3288_vpu_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct rk3288_vpu_ctx, fh); +} + +static inline struct rk3288_vpu_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct rk3288_vpu_ctx, ctrl_handler); +} + +static inline struct rk3288_vpu_buf *vb_to_buf(struct vb2_buffer *vb) +{ + return container_of(vb, struct rk3288_vpu_buf, b); +} + +int rk3288_vpu_ctrls_setup(struct rk3288_vpu_ctx *ctx, + const struct v4l2_ctrl_ops *ctrl_ops, + struct rk3288_vpu_control *controls, + unsigned num_ctrls, + const char *const *(*get_menu)(u32)); +void rk3288_vpu_ctrls_delete(struct rk3288_vpu_ctx *ctx); + +void rk3288_vpu_try_context(struct rk3288_vpu_dev *dev, + struct rk3288_vpu_ctx *ctx); + +void rk3288_vpu_run_done(struct rk3288_vpu_ctx *ctx, + enum vb2_buffer_state result); + +int rk3288_vpu_aux_buf_alloc(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_aux_buf *buf, size_t size); +void rk3288_vpu_aux_buf_free(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_aux_buf *buf); + +/* Register accessors. */ +static inline void vepu_write_relaxed(struct rk3288_vpu_dev *vpu, + u32 val, u32 reg) +{ + vpu_debug(6, "MARK: set reg[%03d]: %08x\n", reg / 4, val); + writel_relaxed(val, vpu->enc_base + reg); +} + +static inline void vepu_write(struct rk3288_vpu_dev *vpu, u32 val, u32 reg) +{ + vpu_debug(6, "MARK: set reg[%03d]: %08x\n", reg / 4, val); + writel(val, vpu->enc_base + reg); +} + +static inline u32 vepu_read(struct rk3288_vpu_dev *vpu, u32 reg) +{ + u32 val = readl(vpu->enc_base + reg); + + vpu_debug(6, "MARK: get reg[%03d]: %08x\n", reg / 4, val); + return val; +} + +#endif /* RK3288_VPU_COMMON_H_ */ diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.c b/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.c new file mode 100644 index 000000000000..3336143f13a5 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.c @@ -0,0 +1,1351 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Rockchip Electronics Co., Ltd. + * Alpha Lin + * Jeffy Chen + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd. + * + * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "rk3288_vpu_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rk3288_vpu_enc.h" +#include "rk3288_vpu_hw.h" + +#define DEF_SRC_FMT_ENC V4L2_PIX_FMT_NV12 +#define DEF_DST_FMT_ENC V4L2_PIX_FMT_VP8 + +#define V4L2_CID_PRIVATE_RK3288_HEADER (V4L2_CID_CUSTOM_BASE + 0) +#define V4L2_CID_PRIVATE_RK3288_REG_PARAMS (V4L2_CID_CUSTOM_BASE + 1) +#define V4L2_CID_PRIVATE_RK3288_HW_PARAMS (V4L2_CID_CUSTOM_BASE + 2) +#define V4L2_CID_PRIVATE_RK3288_RET_PARAMS (V4L2_CID_CUSTOM_BASE + 3) + +static struct rk3288_vpu_fmt formats[] = { + /* Source formats. */ + { + .name = "4:2:0 3 planes Y/Cb/Cr", + .fourcc = V4L2_PIX_FMT_YUV420M, + .codec_mode = RK_VPU_CODEC_NONE, + .num_planes = 3, + .depth = { 8, 4, 4 }, + .enc_fmt = RK3288_VPU_ENC_FMT_YUV420P, + }, + { + .name = "4:2:0 2 plane Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12M, + .codec_mode = RK_VPU_CODEC_NONE, + .num_planes = 2, + .depth = { 8, 8 }, + .enc_fmt = RK3288_VPU_ENC_FMT_YUV420SP, + }, + { + .name = "4:2:2 1 plane YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .codec_mode = RK_VPU_CODEC_NONE, + .num_planes = 1, + .depth = { 16 }, + .enc_fmt = RK3288_VPU_ENC_FMT_YUYV422, + }, + { + .name = "4:2:2 1 plane UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .codec_mode = RK_VPU_CODEC_NONE, + .num_planes = 1, + .depth = { 16 }, + .enc_fmt = RK3288_VPU_ENC_FMT_UYVY422, + }, + /* Destination formats. */ + { + .name = "VP8 Encoded Stream", + .fourcc = V4L2_PIX_FMT_VP8, + .codec_mode = RK_VPU_CODEC_VP8E, + .num_planes = 1, + }, +}; + +static struct rk3288_vpu_fmt *find_format(struct v4l2_format *f, bool bitstream) +{ + unsigned int i; + + vpu_debug_enter(); + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].fourcc != f->fmt.pix_mp.pixelformat) + continue; + if (bitstream && formats[i].codec_mode != RK_VPU_CODEC_NONE) + return &formats[i]; + if (!bitstream && formats[i].codec_mode == RK_VPU_CODEC_NONE) + return &formats[i]; + } + + return NULL; +} + +/* + * Indices of controls that need to be accessed directly, i.e. through + * p_cur.p pointer of their v4l2_ctrl structs. + */ +enum { + RK3288_VPU_ENC_CTRL_HEADER, + RK3288_VPU_ENC_CTRL_REG_PARAMS, + RK3288_VPU_ENC_CTRL_HW_PARAMS, + RK3288_VPU_ENC_CTRL_RET_PARAMS, +}; + +static struct rk3288_vpu_control controls[] = { + /* Private, per-frame controls. */ + [RK3288_VPU_ENC_CTRL_HEADER] = { + .id = V4L2_CID_PRIVATE_RK3288_HEADER, + .type = V4L2_CTRL_TYPE_PRIVATE, + .name = "Rk3288 Private Header", + .elem_size = RK3288_HEADER_SIZE, + .max_stores = VIDEO_MAX_FRAME, + .can_store = true, + }, + [RK3288_VPU_ENC_CTRL_REG_PARAMS] = { + .id = V4L2_CID_PRIVATE_RK3288_REG_PARAMS, + .type = V4L2_CTRL_TYPE_PRIVATE, + .name = "Rk3288 Private Reg Params", + .elem_size = sizeof(struct rk3288_vp8e_reg_params), + .max_stores = VIDEO_MAX_FRAME, + .can_store = true, + }, + [RK3288_VPU_ENC_CTRL_HW_PARAMS] = { + .id = V4L2_CID_PRIVATE_RK3288_HW_PARAMS, + .type = V4L2_CTRL_TYPE_PRIVATE, + .name = "Rk3288 Private Hw Params", + .elem_size = RK3288_HW_PARAMS_SIZE, + .max_stores = VIDEO_MAX_FRAME, + .can_store = true, + }, + [RK3288_VPU_ENC_CTRL_RET_PARAMS] = { + .id = V4L2_CID_PRIVATE_RK3288_RET_PARAMS, + .type = V4L2_CTRL_TYPE_PRIVATE, + .name = "Rk3288 Private Ret Params", + .is_volatile = true, + .is_read_only = true, + .max_stores = VIDEO_MAX_FRAME, + .elem_size = RK3288_RET_PARAMS_SIZE, + }, + /* Generic controls. (currently ignored) */ + { + .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 150, + .step = 1, + .default_value = 30, + }, + { + .id = V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_BITRATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 10000, + .maximum = 288000000, + .step = 1, + .default_value = 2097152, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .maximum = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + .default_value = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) + ), + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .maximum = V4L2_MPEG_VIDEO_H264_LEVEL_4_1, + .default_value = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 30, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 18, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 288000, + .step = 1, + .default_value = 30000, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_SEI_FRAME_PACKING, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Force frame type", + .minimum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED, + .maximum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_NOT_CODED, + .default_value = + V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED, + .menu_skip_mask = 0, + }, + /* + * This hardware does not support features provided by controls + * below, but they are required for compatibility with certain + * userspace software. + */ + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rate Control Reaction Coeff.", + .minimum = 1, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEADER_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE, + .maximum = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + .default_value = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Fixed Target Bit Enable", + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 1, + }, +}; + +static inline const void *get_ctrl_ptr(struct rk3288_vpu_ctx *ctx, unsigned id) +{ + struct v4l2_ctrl *ctrl = ctx->ctrls[id]; + + return ctrl->p_cur.p; +} + +static const char *const *rk3288_vpu_enc_get_menu(u32 id) +{ + static const char *const vpu_video_frame_skip[] = { + "Disabled", + "Level Limit", + "VBV/CPB Limit", + NULL, + }; + static const char *const vpu_video_force_frame[] = { + "Disabled", + "I Frame", + "Not Coded", + NULL, + }; + + switch (id) { + case V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE: + return vpu_video_frame_skip; + case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE: + return vpu_video_force_frame; + } + + return NULL; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct rk3288_vpu_dev *dev = video_drvdata(file); + + vpu_debug_enter(); + + strlcpy(cap->driver, RK3288_VPU_ENC_NAME, sizeof(cap->driver)); + strlcpy(cap->card, dev->pdev->name, sizeof(cap->card)); + strlcpy(cap->bus_info, "platform:" RK3288_VPU_NAME, + sizeof(cap->bus_info)); + + /* + * This is only a mem-to-mem video device. The capture and output + * device capability flags are left only for backward compatibility + * and are scheduled for removal. + */ + cap->capabilities = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + vpu_debug_leave(); + + return 0; +} + +static int vidioc_enum_fmt(struct v4l2_fmtdesc *f, bool out) +{ + struct rk3288_vpu_fmt *fmt; + int i, j = 0; + + vpu_debug_enter(); + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (out && formats[i].codec_mode != RK_VPU_CODEC_NONE) + continue; + else if (!out && formats[i].codec_mode == RK_VPU_CODEC_NONE) + continue; + + if (j == f->index) { + fmt = &formats[i]; + strlcpy(f->description, fmt->name, + sizeof(f->description)); + f->pixelformat = fmt->fourcc; + + f->flags = 0; + if (formats[i].codec_mode != RK_VPU_CODEC_NONE) + f->flags |= V4L2_FMT_FLAG_COMPRESSED; + + vpu_debug_leave(); + + return 0; + } + + ++j; + } + + vpu_debug_leave(); + + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(f, false); +} + +static int vidioc_enum_fmt_vid_out_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(f, true); +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + + vpu_debug_enter(); + + vpu_debug(4, "f->type = %d\n", f->type); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + f->fmt.pix_mp = ctx->dst_fmt; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + f->fmt.pix_mp = ctx->src_fmt; + break; + + default: + vpu_err("invalid buf type\n"); + return -EINVAL; + } + + vpu_debug_leave(); + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct rk3288_vpu_fmt *fmt; + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + char str[5]; + + vpu_debug_enter(); + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + vpu_debug(4, "%s\n", fmt2str(f->fmt.pix_mp.pixelformat, str)); + + fmt = find_format(f, true); + if (!fmt) { + vpu_err("failed to try capture format\n"); + return -EINVAL; + } + + if (pix_fmt_mp->plane_fmt[0].sizeimage == 0) { + vpu_err("must be set encoding output size\n"); + return -EINVAL; + } + + pix_fmt_mp->plane_fmt[0].bytesperline = 0; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + vpu_debug(4, "%s\n", fmt2str(f->fmt.pix_mp.pixelformat, str)); + + fmt = find_format(f, false); + if (!fmt) { + vpu_err("failed to try output format\n"); + return -EINVAL; + } + + if (fmt->num_planes != pix_fmt_mp->num_planes) { + vpu_err("plane number mismatches on output format\n"); + return -EINVAL; + } + + /* Limit to hardware min/max. */ + v4l_bound_align_image(&pix_fmt_mp->width, 8, 1920, 0, + &pix_fmt_mp->height, 4, 1080, 0, 0); + + /* Round up to macroblocks. */ + pix_fmt_mp->width = round_up(pix_fmt_mp->width, MB_DIM); + pix_fmt_mp->height = round_up(pix_fmt_mp->height, MB_DIM); + break; + + default: + vpu_err("invalid buf type\n"); + return -EINVAL; + } + + vpu_debug_leave(); + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + unsigned int mb_width, mb_height; + struct rk3288_vpu_fmt *fmt; + int ret = 0; + int i; + + vpu_debug_enter(); + + /* Change not allowed if any queue is streaming. */ + if (vb2_is_streaming(&ctx->vq_src) || vb2_is_streaming(&ctx->vq_dst)) { + ret = -EBUSY; + goto out; + } + + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + /* + * Pixel format change is not allowed when the other queue has + * buffers allocated. + */ + if (vb2_is_busy(&ctx->vq_src) + && pix_fmt_mp->pixelformat != ctx->dst_fmt.pixelformat) { + ret = -EBUSY; + goto out; + } + + ret = vidioc_try_fmt(file, priv, f); + if (ret) + goto out; + + ctx->vpu_dst_fmt = find_format(f, true); + ctx->dst_fmt = *pix_fmt_mp; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + /* + * Pixel format change is not allowed when the other queue has + * buffers allocated. + */ + if (vb2_is_busy(&ctx->vq_dst) + && pix_fmt_mp->pixelformat != ctx->src_fmt.pixelformat) { + ret = -EBUSY; + goto out; + } + + ret = vidioc_try_fmt(file, priv, f); + if (ret) + goto out; + + fmt = find_format(f, false); + ctx->vpu_src_fmt = fmt; + + mb_width = MB_WIDTH(pix_fmt_mp->width); + mb_height = MB_HEIGHT(pix_fmt_mp->height); + + vpu_debug(0, "OUTPUT codec mode: %d\n", fmt->codec_mode); + vpu_debug(0, "fmt - w: %d, h: %d, mb - w: %d, h: %d\n", + pix_fmt_mp->width, pix_fmt_mp->width, + mb_width, mb_height); + + for (i = 0; i < fmt->num_planes; ++i) { + pix_fmt_mp->plane_fmt[i].bytesperline = + mb_width * MB_DIM * fmt->depth[i] / 8; + pix_fmt_mp->plane_fmt[i].sizeimage = + pix_fmt_mp->plane_fmt[i].bytesperline + * mb_height * MB_DIM; + /* + * All of multiplanar formats we support have chroma + * planes subsampled by 2. + */ + if (i != 0) + pix_fmt_mp->plane_fmt[i].sizeimage /= 2; + } + + /* Reset crop rectangle. */ + ctx->src_crop.width = pix_fmt_mp->width; + ctx->src_crop.height = pix_fmt_mp->height; + + ctx->src_fmt = *pix_fmt_mp; + break; + + default: + vpu_err("invalid buf type\n"); + return -EINVAL; + } + +out: + vpu_debug_leave(); + + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (reqbufs->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + vpu_debug(4, "\n"); + + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + if (ret != 0) { + vpu_err("error in vb2_reqbufs() for E(D)\n"); + goto out; + } + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + vpu_debug(4, "memory type %d\n", reqbufs->memory); + + ret = vb2_reqbufs(&ctx->vq_src, reqbufs); + if (ret != 0) { + vpu_err("error in vb2_reqbufs() for E(S)\n"); + goto out; + } + break; + + default: + vpu_err("invalid buf type\n"); + ret = -EINVAL; + goto out; + } + +out: + vpu_debug_leave(); + + return ret; +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (buf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_querybuf(&ctx->vq_dst, buf); + if (ret != 0) { + vpu_err("error in vb2_querybuf() for E(D)\n"); + goto out; + } + + buf->m.planes[0].m.mem_offset += DST_QUEUE_OFF_BASE; + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_querybuf(&ctx->vq_src, buf); + if (ret != 0) { + vpu_err("error in vb2_querybuf() for E(S)\n"); + goto out; + } + break; + + default: + vpu_err("invalid buf type\n"); + ret = -EINVAL; + goto out; + } + +out: + vpu_debug_leave(); + + return ret; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + int i; + + vpu_debug_enter(); + + for (i = 0; i < buf->length; i++) { + vpu_debug(4, "plane[%d]->length %d bytesused %d\n", + i, buf->m.planes[i].length, + buf->m.planes[i].bytesused); + } + + switch (buf->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_qbuf(&ctx->vq_src, buf); + vpu_debug(4, "vb2_qbuf return %d\n", ret); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_qbuf(&ctx->vq_dst, buf); + vpu_debug(4, "vb2_qbuf return %d\n", ret); + break; + + default: + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (buf->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_dqbuf(&ctx->vq_src, buf, file->f_flags & O_NONBLOCK); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_dqbuf(&ctx->vq_dst, buf, file->f_flags & O_NONBLOCK); + break; + + default: + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int vidioc_expbuf(struct file *file, void *priv, + struct v4l2_exportbuffer *eb) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (eb->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_expbuf(&ctx->vq_src, eb); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_expbuf(&ctx->vq_dst, eb); + break; + + default: + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_streamon(&ctx->vq_src, type); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_streamon(&ctx->vq_dst, type); + break; + + default: + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret; + + vpu_debug_enter(); + + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + ret = vb2_streamoff(&ctx->vq_src, type); + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_streamoff(&ctx->vq_dst, type); + break; + + default: + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int rk3288_vpu_enc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rk3288_vpu_ctx *ctx = ctrl_to_ctx(ctrl); + struct rk3288_vpu_dev *dev = ctx->dev; + int ret = 0; + + vpu_debug_enter(); + + vpu_debug(4, "ctrl id %d\n", ctrl->id); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + case V4L2_CID_MPEG_VIDEO_BITRATE: + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: + case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: + case V4L2_CID_MPEG_VIDEO_H264_SEI_FRAME_PACKING: + case V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF: + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + case V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT: + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE: + /* Ignore these controls for now. (FIXME?) */ + break; + + case V4L2_CID_PRIVATE_RK3288_HEADER: + case V4L2_CID_PRIVATE_RK3288_REG_PARAMS: + case V4L2_CID_PRIVATE_RK3288_HW_PARAMS: + /* Nothing to do here. The control is used directly. */ + break; + + default: + v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int rk3288_vpu_enc_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rk3288_vpu_ctx *ctx = ctrl_to_ctx(ctrl); + struct rk3288_vpu_dev *dev = ctx->dev; + int ret = 0; + + vpu_debug_enter(); + + vpu_debug(4, "ctrl id %d\n", ctrl->id); + + switch (ctrl->id) { + case V4L2_CID_PRIVATE_RK3288_RET_PARAMS: + memcpy(ctrl->p_new.p, ctx->run.priv_dst.cpu, + RK3288_RET_PARAMS_SIZE); + break; + + default: + v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static const struct v4l2_ctrl_ops rk3288_vpu_enc_ctrl_ops = { + .s_ctrl = rk3288_vpu_enc_s_ctrl, + .g_volatile_ctrl = rk3288_vpu_enc_g_volatile_ctrl, +}; + +static int vidioc_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cap) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt; + int ret = 0; + + vpu_debug_enter(); + + /* Crop only supported on source. */ + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = -EINVAL; + goto out; + } + + cap->bounds.left = 0; + cap->bounds.top = 0; + cap->bounds.width = fmt->width; + cap->bounds.height = fmt->height; + cap->defrect = cap->bounds; + cap->pixelaspect.numerator = 1; + cap->pixelaspect.denominator = 1; + +out: + vpu_debug_leave(); + + return ret; +} + +static int vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + int ret = 0; + + vpu_debug_enter(); + + /* Crop only supported on source. */ + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = -EINVAL; + goto out; + } + + crop->c = ctx->src_crop; + +out: + vpu_debug_leave(); + + return ret; +} + +static int vidioc_s_crop(struct file *file, void *priv, + const struct v4l2_crop *crop) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt; + const struct v4l2_rect *rect = &crop->c; + int ret = 0; + + vpu_debug_enter(); + + /* Crop only supported on source. */ + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = -EINVAL; + goto out; + } + + /* Change not allowed if the queue is streaming. */ + if (vb2_is_streaming(&ctx->vq_src)) { + ret = -EBUSY; + goto out; + } + + /* We do not support offsets. */ + if (rect->left != 0 || rect->top != 0) + goto fallback; + + /* We can crop only inside right- or bottom-most macroblocks. */ + if (round_up(rect->width, MB_DIM) != fmt->width + || round_up(rect->height, MB_DIM) != fmt->height) + goto fallback; + + /* We support widths aligned to 4 pixels and arbitrary heights. */ + ctx->src_crop.width = round_up(rect->width, 4); + ctx->src_crop.height = rect->height; + + vpu_debug_leave(); + + return 0; + +fallback: + /* Default to full frame for incorrect settings. */ + ctx->src_crop.width = fmt->width; + ctx->src_crop.height = fmt->height; + +out: + vpu_debug_leave(); + + return ret; +} + +static const struct v4l2_ioctl_ops rk3288_vpu_enc_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_cap_mplane, + .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out_mplane, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_expbuf = vidioc_expbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_cropcap = vidioc_cropcap, + .vidioc_g_crop = vidioc_g_crop, + .vidioc_s_crop = vidioc_s_crop, +}; + +static int rk3288_vpu_queue_setup(struct vb2_queue *vq, + const struct v4l2_format *fmt, + unsigned int *buf_count, + unsigned int *plane_count, + unsigned int psize[], void *allocators[]) +{ + struct rk3288_vpu_ctx *ctx = fh_to_ctx(vq->drv_priv); + int ret = 0; + int i; + + vpu_debug_enter(); + + switch (vq->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *plane_count = ctx->vpu_dst_fmt->num_planes; + + if (*buf_count < 1) + *buf_count = 1; + + if (*buf_count > VIDEO_MAX_FRAME) + *buf_count = VIDEO_MAX_FRAME; + + psize[0] = ctx->dst_fmt.plane_fmt[0].sizeimage; + allocators[0] = ctx->dev->alloc_ctx; + vpu_debug(0, "capture psize[%d]: %d\n", 0, psize[0]); + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *plane_count = ctx->vpu_src_fmt->num_planes; + + if (*buf_count < 1) + *buf_count = 1; + + if (*buf_count > VIDEO_MAX_FRAME) + *buf_count = VIDEO_MAX_FRAME; + + for (i = 0; i < ctx->vpu_src_fmt->num_planes; ++i) { + psize[i] = ctx->src_fmt.plane_fmt[i].sizeimage; + vpu_debug(0, "output psize[%d]: %d\n", i, psize[i]); + allocators[i] = ctx->dev->alloc_ctx; + } + break; + + default: + vpu_err("invalid queue type: %d\n", vq->type); + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static int rk3288_vpu_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(vq->drv_priv); + int ret = 0; + int i; + + vpu_debug_enter(); + + switch (vq->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + vpu_debug(4, "plane size: %ld, dst size: %d\n", + vb2_plane_size(vb, 0), + ctx->dst_fmt.plane_fmt[0].sizeimage); + + if (vb2_plane_size(vb, 0) + < ctx->dst_fmt.plane_fmt[0].sizeimage) { + vpu_err("plane size is too small for capture\n"); + ret = -EINVAL; + } + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + for (i = 0; i < ctx->vpu_src_fmt->num_planes; ++i) { + vpu_debug(4, "plane %d size: %ld, sizeimage: %u\n", i, + vb2_plane_size(vb, i), + ctx->src_fmt.plane_fmt[i].sizeimage); + + if (vb2_plane_size(vb, i) + < ctx->src_fmt.plane_fmt[i].sizeimage) { + vpu_err("size of plane %d is too small for output\n", + i); + break; + } + } + + if (i != ctx->vpu_src_fmt->num_planes) + ret = -EINVAL; + break; + + default: + vpu_err("invalid queue type: %d\n", vq->type); + ret = -EINVAL; + } + + vpu_debug_leave(); + + return ret; +} + +static void rk3288_vpu_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(vq->drv_priv); + + vpu_debug_enter(); + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE + && vb->state == VB2_BUF_STATE_DONE + && ctx->vpu_dst_fmt->fourcc == V4L2_PIX_FMT_VP8) { + struct rk3288_vpu_buf *buf; + + buf = vb_to_buf(vb); + rk3288_vpu_vp8e_assemble_bitstream(ctx, buf); + } + + vpu_debug_leave(); +} + +static int rk3288_vpu_start_streaming(struct vb2_queue *q, unsigned int count) +{ + int ret = 0; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(q->drv_priv); + struct rk3288_vpu_dev *dev = ctx->dev; + bool ready = false; + + vpu_debug_enter(); + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = rk3288_vpu_init(ctx); + if (ret < 0) { + vpu_err("rk3288_vpu_init failed\n"); + return ret; + } + + ready = vb2_is_streaming(&ctx->vq_src); + } else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ready = vb2_is_streaming(&ctx->vq_dst); + } + + if (ready) + rk3288_vpu_try_context(dev, ctx); + + vpu_debug_leave(); + + return 0; +} + +static void rk3288_vpu_stop_streaming(struct vb2_queue *q) +{ + unsigned long flags; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(q->drv_priv); + struct rk3288_vpu_dev *dev = ctx->dev; + struct rk3288_vpu_buf *b; + LIST_HEAD(queue); + int i; + + vpu_debug_enter(); + + spin_lock_irqsave(&dev->irqlock, flags); + + list_del_init(&ctx->list); + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + list_splice_init(&ctx->dst_queue, &queue); + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + list_splice_init(&ctx->src_queue, &queue); + break; + + default: + break; + } + + spin_unlock_irqrestore(&dev->irqlock, flags); + + wait_event(dev->run_wq, dev->current_ctx != ctx); + + while (!list_empty(&queue)) { + b = list_first_entry(&queue, struct rk3288_vpu_buf, list); + for (i = 0; i < b->b.num_planes; i++) + vb2_set_plane_payload(&b->b, i, 0); + vb2_buffer_done(&b->b, VB2_BUF_STATE_ERROR); + list_del(&b->list); + } + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + rk3288_vpu_deinit(ctx); + + vpu_debug_leave(); +} + +static void rk3288_vpu_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct rk3288_vpu_ctx *ctx = fh_to_ctx(vq->drv_priv); + struct rk3288_vpu_dev *dev = ctx->dev; + struct rk3288_vpu_buf *vpu_buf; + unsigned long flags; + + vpu_debug_enter(); + + switch (vq->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + vpu_buf = vb_to_buf(vb); + + /* Mark destination as available for use by VPU */ + spin_lock_irqsave(&dev->irqlock, flags); + + list_add_tail(&vpu_buf->list, &ctx->dst_queue); + + spin_unlock_irqrestore(&dev->irqlock, flags); + break; + + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + vpu_buf = vb_to_buf(vb); + + spin_lock_irqsave(&dev->irqlock, flags); + + list_add_tail(&vpu_buf->list, &ctx->src_queue); + + spin_unlock_irqrestore(&dev->irqlock, flags); + break; + + default: + vpu_err("unsupported buffer type (%d)\n", vq->type); + } + + if (vb2_is_streaming(&ctx->vq_src) && vb2_is_streaming(&ctx->vq_dst)) + rk3288_vpu_try_context(dev, ctx); + + vpu_debug_leave(); +} + +static struct vb2_ops rk3288_vpu_enc_qops = { + .queue_setup = rk3288_vpu_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = rk3288_vpu_buf_prepare, + .buf_finish = rk3288_vpu_buf_finish, + .start_streaming = rk3288_vpu_start_streaming, + .stop_streaming = rk3288_vpu_stop_streaming, + .buf_queue = rk3288_vpu_buf_queue, +}; + +struct vb2_ops *get_enc_queue_ops(void) +{ + return &rk3288_vpu_enc_qops; +} + +const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void) +{ + return &rk3288_vpu_enc_ioctl_ops; +} + +static void rk3288_vpu_enc_prepare_run(struct rk3288_vpu_ctx *ctx) +{ + struct vb2_buffer *vb2_src = &ctx->run.src->b; + unsigned config_store = vb2_src->v4l2_buf.config_store; + + v4l2_ctrl_apply_store(&ctx->ctrl_handler, config_store); + + memcpy(ctx->run.dst->vp8e.header, + get_ctrl_ptr(ctx, RK3288_VPU_ENC_CTRL_HEADER), + RK3288_HEADER_SIZE); + ctx->run.vp8e.reg_params = get_ctrl_ptr(ctx, + RK3288_VPU_ENC_CTRL_REG_PARAMS); + memcpy(ctx->run.priv_src.cpu, + get_ctrl_ptr(ctx, RK3288_VPU_ENC_CTRL_HW_PARAMS), + RK3288_HW_PARAMS_SIZE); +} + +static const struct rk3288_vpu_run_ops rk3288_vpu_enc_run_ops = { + .prepare_run = rk3288_vpu_enc_prepare_run, +}; + +int rk3288_vpu_enc_init(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + struct v4l2_format f; + int ret; + + f.fmt.pix_mp.pixelformat = DEF_SRC_FMT_ENC; + ctx->vpu_src_fmt = find_format(&f, false); + f.fmt.pix_mp.pixelformat = DEF_DST_FMT_ENC; + ctx->vpu_dst_fmt = find_format(&f, true); + + ret = rk3288_vpu_aux_buf_alloc(vpu, &ctx->run.priv_src, + RK3288_HW_PARAMS_SIZE); + if (ret) { + vpu_err("Failed to allocate private source buffer.\n"); + return ret; + } + + + ret = rk3288_vpu_aux_buf_alloc(vpu, &ctx->run.priv_dst, + RK3288_RET_PARAMS_SIZE); + if (ret) { + vpu_err("Failed to allocate private destination buffer.\n"); + goto err_priv_src; + } + + ret = rk3288_vpu_ctrls_setup(ctx, &rk3288_vpu_enc_ctrl_ops, + controls, ARRAY_SIZE(controls), + rk3288_vpu_enc_get_menu); + if (ret) { + vpu_err("Failed to set up controls\n"); + goto err_priv_dst; + } + + ctx->run_ops = &rk3288_vpu_enc_run_ops; + + return 0; + +err_priv_dst: + rk3288_vpu_aux_buf_free(vpu, &ctx->run.priv_dst); +err_priv_src: + rk3288_vpu_aux_buf_free(vpu, &ctx->run.priv_src); + + return ret; +} + +void rk3288_vpu_enc_exit(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + + rk3288_vpu_ctrls_delete(ctx); + + rk3288_vpu_aux_buf_free(vpu, &ctx->run.priv_dst); + rk3288_vpu_aux_buf_free(vpu, &ctx->run.priv_src); +} diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.h b/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.h new file mode 100644 index 000000000000..80b71c2a5979 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_enc.h @@ -0,0 +1,34 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (c) 2014 Rockchip Electronics Co., Ltd. + * Alpha Lin + * Jeffy Chen + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd. + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef RK3288_VPU_ENC_H_ +#define RK3288_VPU_ENC_H_ + +struct vb2_ops *get_enc_queue_ops(void); +const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void); +struct rk3288_vpu_fmt *get_enc_def_fmt(bool src); +int rk3288_vpu_enc_init(struct rk3288_vpu_ctx *ctx); +void rk3288_vpu_enc_exit(struct rk3288_vpu_ctx *ctx); + +#endif /* RK3288_VPU_ENC_H_ */ diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.c b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.c new file mode 100644 index 000000000000..b9a33bddfd0c --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.c @@ -0,0 +1,353 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "rk3288_vpu_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rk3288_vpu_regs.h" + +/** + * struct rk3288_vpu_variant - information about VPU hardware variant + * + * @hw_id: Top 16 bits (product ID) of hardware ID register. + * @enc_offset: Offset from VPU base to encoder registers. + * @enc_reg_num: Number of registers of encoder block. + * @dec_offset: Offset from VPU base to decoder registers. + * @dec_reg_num: Number of registers of decoder block. + */ +struct rk3288_vpu_variant { + u16 hw_id; + unsigned enc_offset; + unsigned enc_reg_num; + unsigned dec_offset; + unsigned dec_reg_num; +}; + +/* Supported VPU variants. */ +static const struct rk3288_vpu_variant rk3288_vpu_variants[] = { + { + .hw_id = 0x4831, + .enc_offset = 0x0, + .enc_reg_num = 164, + .dec_offset = 0x400, + .dec_reg_num = 60 + 41, + }, +}; + +/** + * struct rk3288_vpu_codec_ops - codec mode specific operations + * + * @init: Prepare for streaming. Called from VB2 .start_streaming() + * when streaming from both queues is being enabled. + * @exit: Clean-up after streaming. Called from VB2 .stop_streaming() + * when streaming from first of both enabled queues is being + * disabled. + * @run: Start single {en,de)coding run. Called from non-atomic context + * to indicate that a pair of buffers is ready and the hardware + * should be programmed and started. + * @done: Read back processing results and additional data from hardware. + * @reset: Reset the hardware in case of a timeout. + */ +struct rk3288_vpu_codec_ops { + int (*init)(struct rk3288_vpu_ctx *); + void (*exit)(struct rk3288_vpu_ctx *); + + void (*run)(struct rk3288_vpu_ctx *); + void (*done)(struct rk3288_vpu_ctx *, enum vb2_buffer_state); + void (*reset)(struct rk3288_vpu_ctx *); +}; + +/* + * Hardware control routines. + */ + +static int rk3288_vpu_identify(struct rk3288_vpu_dev *vpu) +{ + u32 hw_id; + int i; + + hw_id = readl(vpu->base) >> 16; + + dev_info(vpu->dev, "Read hardware ID: %x\n", hw_id); + + for (i = 0; i < ARRAY_SIZE(rk3288_vpu_variants); ++i) { + if (hw_id == rk3288_vpu_variants[i].hw_id) { + vpu->variant = &rk3288_vpu_variants[i]; + return 0; + } + } + + return -ENOENT; +} + +void rk3288_vpu_power_on(struct rk3288_vpu_dev *vpu) +{ + vpu_debug_enter(); + + /* TODO: Clock gating. */ + + pm_runtime_get_sync(vpu->dev); + + vpu_debug_leave(); +} + +static void rk3288_vpu_power_off(struct rk3288_vpu_dev *vpu) +{ + vpu_debug_enter(); + + pm_runtime_mark_last_busy(vpu->dev); + pm_runtime_put_autosuspend(vpu->dev); + + /* TODO: Clock gating. */ + + vpu_debug_leave(); +} + +/* + * Interrupt handlers. + */ + +static irqreturn_t vepu_irq(int irq, void *dev_id) +{ + struct rk3288_vpu_dev *vpu = dev_id; + u32 status = vepu_read(vpu, VEPU_REG_INTERRUPT); + + vepu_write(vpu, 0, VEPU_REG_INTERRUPT); + + if (status & VEPU_REG_INTERRUPT_BIT) { + struct rk3288_vpu_ctx *ctx = vpu->current_ctx; + + vepu_write(vpu, 0, VEPU_REG_AXI_CTRL); + rk3288_vpu_power_off(vpu); + cancel_delayed_work(&vpu->watchdog_work); + + ctx->hw.codec_ops->done(ctx, VB2_BUF_STATE_DONE); + } + + return IRQ_HANDLED; +} + +static void rk3288_vpu_watchdog(struct work_struct *work) +{ + struct rk3288_vpu_dev *vpu = container_of(to_delayed_work(work), + struct rk3288_vpu_dev, watchdog_work); + struct rk3288_vpu_ctx *ctx = vpu->current_ctx; + unsigned long flags; + + spin_lock_irqsave(&vpu->irqlock, flags); + + ctx->hw.codec_ops->reset(ctx); + + spin_unlock_irqrestore(&vpu->irqlock, flags); + + vpu_err("frame processing timed out!\n"); + + rk3288_vpu_power_off(vpu); + ctx->hw.codec_ops->done(ctx, VB2_BUF_STATE_ERROR); +} + +/* + * Initialization/clean-up. + */ + +#if defined(CONFIG_ROCKCHIP_IOMMU) +static int rk3288_vpu_iommu_init(struct rk3288_vpu_dev *vpu) +{ + int ret; + + vpu->mapping = arm_iommu_create_mapping(&platform_bus_type, + 0x10000000, SZ_2G); + if (IS_ERR(vpu->mapping)) { + ret = PTR_ERR(vpu->mapping); + return ret; + } + + vpu->dev->dma_parms = devm_kzalloc(vpu->dev, + sizeof(*vpu->dev->dma_parms), GFP_KERNEL); + if (!vpu->dev->dma_parms) + goto err_release_mapping; + + dma_set_max_seg_size(vpu->dev, 0xffffffffu); + + ret = arm_iommu_attach_device(vpu->dev, vpu->mapping); + if (ret) + goto err_release_mapping; + + return 0; + +err_release_mapping: + arm_iommu_release_mapping(vpu->mapping); + + return ret; +} + +static void rk3288_vpu_iommu_cleanup(struct rk3288_vpu_dev *vpu) +{ + arm_iommu_detach_device(vpu->dev); + arm_iommu_release_mapping(vpu->mapping); +} +#else +static inline int rk3288_vpu_iommu_init(struct rk3288_vpu_dev *vpu) +{ + return 0; +} + +static inline void rk3288_vpu_iommu_cleanup(struct rk3288_vpu_dev *vpu) { } +#endif + +int rk3288_vpu_hw_probe(struct rk3288_vpu_dev *vpu) +{ + struct resource *res; + int irq_enc; + int ret; + + pr_info("probe device %s\n", dev_name(vpu->dev)); + + INIT_DELAYED_WORK(&vpu->watchdog_work, rk3288_vpu_watchdog); + + vpu->aclk_vcodec = devm_clk_get(vpu->dev, "aclk_vcodec"); + if (IS_ERR(vpu->aclk_vcodec)) { + dev_err(vpu->dev, "failed to get aclk_vcodec\n"); + return PTR_ERR(vpu->aclk_vcodec); + } + + vpu->hclk_vcodec = devm_clk_get(vpu->dev, "hclk_vcodec"); + if (IS_ERR(vpu->hclk_vcodec)) { + dev_err(vpu->dev, "failed to get hclk_vcodec\n"); + return PTR_ERR(vpu->hclk_vcodec); + } + + res = platform_get_resource(vpu->pdev, IORESOURCE_MEM, 0); + vpu->base = devm_ioremap_resource(vpu->dev, res); + if (IS_ERR(vpu->base)) + return PTR_ERR(vpu->base); + + clk_prepare_enable(vpu->aclk_vcodec); + clk_prepare_enable(vpu->hclk_vcodec); + + ret = rk3288_vpu_identify(vpu); + if (ret < 0) { + dev_err(vpu->dev, "failed to identify hardware variant\n"); + goto err_power; + } + + vpu->enc_base = vpu->base + vpu->variant->enc_offset; + vpu->dec_base = vpu->base + vpu->variant->dec_offset; + + ret = dma_set_coherent_mask(vpu->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(vpu->dev, "could not set DMA coherent mask\n"); + goto err_power; + } + + ret = rk3288_vpu_iommu_init(vpu); + if (ret) + goto err_power; + + irq_enc = platform_get_irq_byname(vpu->pdev, "vepu"); + if (irq_enc <= 0) { + dev_err(vpu->dev, "could not get vepu IRQ\n"); + ret = -ENXIO; + goto err_iommu; + } + + ret = devm_request_threaded_irq(vpu->dev, irq_enc, NULL, vepu_irq, + IRQF_ONESHOT, dev_name(vpu->dev), vpu); + if (ret) { + dev_err(vpu->dev, "could not request vepu IRQ\n"); + goto err_iommu; + } + + pm_runtime_set_autosuspend_delay(vpu->dev, 100); + pm_runtime_use_autosuspend(vpu->dev); + pm_runtime_enable(vpu->dev); + + return 0; + +err_iommu: + rk3288_vpu_iommu_cleanup(vpu); +err_power: + clk_disable_unprepare(vpu->hclk_vcodec); + clk_disable_unprepare(vpu->aclk_vcodec); + + return ret; +} + +void rk3288_vpu_hw_remove(struct rk3288_vpu_dev *vpu) +{ + rk3288_vpu_iommu_cleanup(vpu); + + pm_runtime_disable(vpu->dev); + + clk_disable_unprepare(vpu->hclk_vcodec); + clk_disable_unprepare(vpu->aclk_vcodec); +} + +static void rk3288_vpu_enc_reset(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + + vepu_write(vpu, VEPU_REG_INTERRUPT_DIS_BIT, VEPU_REG_INTERRUPT); + vepu_write(vpu, 0, VEPU_REG_ENC_CTRL); + vepu_write(vpu, 0, VEPU_REG_AXI_CTRL); +} + +static const struct rk3288_vpu_codec_ops mode_ops[] = { + [RK_VPU_CODEC_VP8E] = { + .init = rk3288_vpu_vp8e_init, + .exit = rk3288_vpu_vp8e_exit, + .run = rk3288_vpu_vp8e_run, + .done = rk3288_vpu_vp8e_done, + .reset = rk3288_vpu_enc_reset, + }, +}; + +void rk3288_vpu_run(struct rk3288_vpu_ctx *ctx) +{ + ctx->hw.codec_ops->run(ctx); +} + +int rk3288_vpu_init(struct rk3288_vpu_ctx *ctx) +{ + enum rk3288_vpu_codec_mode codec_mode; + + if (ctx->vpu_dst_fmt->codec_mode != RK_VPU_CODEC_NONE) + codec_mode = ctx->vpu_dst_fmt->codec_mode; /* Encoder */ + else + codec_mode = ctx->vpu_src_fmt->codec_mode; /* Decoder */ + + ctx->hw.codec_ops = &mode_ops[codec_mode]; + + return ctx->hw.codec_ops->init(ctx); +} + +void rk3288_vpu_deinit(struct rk3288_vpu_ctx *ctx) +{ + ctx->hw.codec_ops->exit(ctx); +} diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.h b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.h new file mode 100644 index 000000000000..87cb03cb2e85 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw.h @@ -0,0 +1,149 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef RK3288_VPU_HW_H_ +#define RK3288_VPU_HW_H_ + +#include + +#define RK3288_HEADER_SIZE 256 +#define RK3288_HW_PARAMS_SIZE 5487 +#define RK3288_RET_PARAMS_SIZE 488 + +struct rk3288_vpu_dev; +struct rk3288_vpu_ctx; +struct rk3288_vpu_buf; + +struct rk3288_vpu_h264d_priv_tbl; + +/** + * enum rk3288_vpu_enc_fmt - source format ID for hardware registers. + */ +enum rk3288_vpu_enc_fmt { + RK3288_VPU_ENC_FMT_YUV420P = 0, + RK3288_VPU_ENC_FMT_YUV420SP = 1, + RK3288_VPU_ENC_FMT_YUYV422 = 2, + RK3288_VPU_ENC_FMT_UYVY422 = 3, +}; + +/** + * struct rk3288_vp8e_reg_params - low level encoding parameters + * TODO: Create abstract structures for more generic controls or just + * remove unused fields. + */ +struct rk3288_vp8e_reg_params { + u32 unused_00[5]; + u32 hdr_len; + u32 unused_18[8]; + u32 enc_ctrl; + u32 unused_3c; + u32 enc_ctrl0; + u32 enc_ctrl1; + u32 enc_ctrl2; + u32 enc_ctrl3; + u32 enc_ctrl5; + u32 enc_ctrl4; + u32 str_hdr_rem_msb; + u32 str_hdr_rem_lsb; + u32 unused_60; + u32 mad_ctrl; + u32 unused_68; + u32 qp_val[8]; + u32 bool_enc; + u32 vp8_ctrl0; + u32 rlc_ctrl; + u32 mb_ctrl; + u32 unused_9c[14]; + u32 rgb_yuv_coeff[2]; + u32 rgb_mask_msb; + u32 intra_area_ctrl; + u32 cir_intra_ctrl; + u32 unused_e8[2]; + u32 first_roi_area; + u32 second_roi_area; + u32 mvc_ctrl; + u32 unused_fc; + u32 intra_penalty[7]; + u32 unused_11c; + u32 seg_qp[24]; + u32 dmv_4p_1p_penalty[32]; + u32 dmv_qpel_penalty[32]; + u32 vp8_ctrl1; + u32 bit_cost_golden; + u32 loop_flt_delta[2]; +}; + +/** + * struct rk3288_vpu_aux_buf - auxiliary DMA buffer for hardware data + * @cpu: CPU pointer to the buffer. + * @dma: DMA address of the buffer. + * @size: Size of the buffer. + */ +struct rk3288_vpu_aux_buf { + void *cpu; + dma_addr_t dma; + size_t size; +}; + +/** + * struct rk3288_vpu_vp8e_hw_ctx - Context private data specific to codec mode. + * @ctrl_buf: VP8 control buffer. + * @ext_buf: VP8 ext data buffer. + * @mv_buf: VP8 motion vector buffer. + * @ref_rec_ptr: Bit flag for swapping ref and rec buffers every frame. + */ +struct rk3288_vpu_vp8e_hw_ctx { + struct rk3288_vpu_aux_buf ctrl_buf; + struct rk3288_vpu_aux_buf ext_buf; + struct rk3288_vpu_aux_buf mv_buf; + u8 ref_rec_ptr:1; +}; + +/** + * struct rk3288_vpu_hw_ctx - Context private data of hardware code. + * @codec_ops: Set of operations associated with current codec mode. + */ +struct rk3288_vpu_hw_ctx { + const struct rk3288_vpu_codec_ops *codec_ops; + + /* Specific for particular codec modes. */ + union { + struct rk3288_vpu_vp8e_hw_ctx vp8e; + /* Other modes will need different data. */ + }; +}; + +int rk3288_vpu_hw_probe(struct rk3288_vpu_dev *vpu); +void rk3288_vpu_hw_remove(struct rk3288_vpu_dev *vpu); + +int rk3288_vpu_init(struct rk3288_vpu_ctx *ctx); +void rk3288_vpu_deinit(struct rk3288_vpu_ctx *ctx); + +void rk3288_vpu_run(struct rk3288_vpu_ctx *ctx); + +void rk3288_vpu_power_on(struct rk3288_vpu_dev *vpu); + +/* Run ops for VP8 encoder */ +int rk3288_vpu_vp8e_init(struct rk3288_vpu_ctx *ctx); +void rk3288_vpu_vp8e_exit(struct rk3288_vpu_ctx *ctx); +void rk3288_vpu_vp8e_run(struct rk3288_vpu_ctx *ctx); +void rk3288_vpu_vp8e_done(struct rk3288_vpu_ctx *ctx, + enum vb2_buffer_state result); + +void rk3288_vpu_vp8e_assemble_bitstream(struct rk3288_vpu_ctx *ctx, + struct rk3288_vpu_buf *dst_buf); + +#endif /* RK3288_VPU_HW_H_ */ diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw_vp8e.c b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw_vp8e.c new file mode 100644 index 000000000000..25684d32c233 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_hw_vp8e.c @@ -0,0 +1,410 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Rockchip Electronics Co., Ltd. + * Alpha Lin + * Jeffy Chen + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 "rk3288_vpu_common.h" + +#include +#include + +#include "rk3288_vpu_regs.h" +#include "rk3288_vpu_hw.h" + +/* Various parameters specific to VP8 encoder. */ +#define VP8_CABAC_CTX_OFFSET 192 +#define VP8_CABAC_CTX_SIZE ((55 + 96) << 3) + +#define VP8_KEY_FRAME_HDR_SIZE 10 +#define VP8_INTER_FRAME_HDR_SIZE 3 + +#define VP8_FRAME_TAG_KEY_FRAME_BIT BIT(0) +#define VP8_FRAME_TAG_LENGTH_SHIFT 5 +#define VP8_FRAME_TAG_LENGTH_MASK (0x7ffff << 5) + +/** + * struct rk3288_vpu_vp8e_ctrl_buf - hardware control buffer layout + * @ext_hdr_size: Ext header size in bytes (written by hardware). + * @dct_size: DCT partition size (written by hardware). + * @rsvd: Reserved for hardware. + */ +struct rk3288_vpu_vp8e_ctrl_buf { + u32 ext_hdr_size; + u32 dct_size; + u8 rsvd[1016]; +}; + +/* + * The hardware takes care only of ext hdr and dct partition. The software + * must take care of frame header. + * + * Buffer layout as received from hardware: + * |<--gap-->|<--ext hdr-->|<-gap->|<---dct part--- + * |<-------dct part offset------->| + * + * Required buffer layout: + * |<--hdr-->|<--ext hdr-->|<---dct part--- + */ +void rk3288_vpu_vp8e_assemble_bitstream(struct rk3288_vpu_ctx *ctx, + struct rk3288_vpu_buf *dst_buf) +{ + size_t ext_hdr_size = dst_buf->vp8e.ext_hdr_size; + size_t dct_size = dst_buf->vp8e.dct_size; + size_t hdr_size = dst_buf->vp8e.hdr_size; + size_t dst_size; + size_t tag_size; + void *dst; + u32 *tag; + + dst_size = vb2_plane_size(&dst_buf->b, 0); + dst = vb2_plane_vaddr(&dst_buf->b, 0); + tag = dst; /* To access frame tag words. */ + + if (WARN_ON(hdr_size + ext_hdr_size + dct_size > dst_size)) + return; + if (WARN_ON(dst_buf->vp8e.dct_offset + dct_size > dst_size)) + return; + + vpu_debug(1, "%s: hdr_size = %u, ext_hdr_size = %u, dct_size = %u\n", + __func__, hdr_size, ext_hdr_size, dct_size); + + memmove(dst + hdr_size + ext_hdr_size, + dst + dst_buf->vp8e.dct_offset, dct_size); + memcpy(dst, dst_buf->vp8e.header, hdr_size); + + /* Patch frame tag at first 32-bit word of the frame. */ + if (dst_buf->b.v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) { + tag_size = VP8_KEY_FRAME_HDR_SIZE; + tag[0] &= ~VP8_FRAME_TAG_KEY_FRAME_BIT; + } else { + tag_size = VP8_INTER_FRAME_HDR_SIZE; + tag[0] |= VP8_FRAME_TAG_KEY_FRAME_BIT; + } + + tag[0] &= ~VP8_FRAME_TAG_LENGTH_MASK; + tag[0] |= (hdr_size + ext_hdr_size - tag_size) + << VP8_FRAME_TAG_LENGTH_SHIFT; + + vb2_set_plane_payload(&dst_buf->b, 0, + hdr_size + ext_hdr_size + dct_size); +} + +static inline unsigned int ref_luma_size(unsigned int w, unsigned int h) +{ + return round_up(w, MB_DIM) * round_up(h, MB_DIM); +} + +int rk3288_vpu_vp8e_init(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + size_t height = ctx->src_fmt.height; + size_t width = ctx->src_fmt.width; + size_t ref_buf_size; + size_t mv_size; + int ret; + + ret = rk3288_vpu_aux_buf_alloc(vpu, &ctx->hw.vp8e.ctrl_buf, + sizeof(struct rk3288_vpu_vp8e_ctrl_buf)); + if (ret) { + vpu_err("failed to allocate ctrl buffer\n"); + return ret; + } + + mv_size = DIV_ROUND_UP(width, 16) * DIV_ROUND_UP(height, 16) / 4; + ret = rk3288_vpu_aux_buf_alloc(vpu, &ctx->hw.vp8e.mv_buf, mv_size); + if (ret) { + vpu_err("failed to allocate MV buffer\n"); + goto err_ctrl_buf; + } + + ref_buf_size = ref_luma_size(width, height) * 3 / 2; + ret = rk3288_vpu_aux_buf_alloc(vpu, &ctx->hw.vp8e.ext_buf, + 2 * ref_buf_size); + if (ret) { + vpu_err("failed to allocate ext buffer\n"); + goto err_mv_buf; + } + + return 0; + +err_mv_buf: + rk3288_vpu_aux_buf_free(vpu, &ctx->hw.vp8e.mv_buf); +err_ctrl_buf: + rk3288_vpu_aux_buf_free(vpu, &ctx->hw.vp8e.ctrl_buf); + + return ret; +} + +void rk3288_vpu_vp8e_exit(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + + rk3288_vpu_aux_buf_free(vpu, &ctx->hw.vp8e.ext_buf); + rk3288_vpu_aux_buf_free(vpu, &ctx->hw.vp8e.mv_buf); + rk3288_vpu_aux_buf_free(vpu, &ctx->hw.vp8e.ctrl_buf); +} + +static inline u32 enc_in_img_ctrl(struct rk3288_vpu_ctx *ctx) +{ + struct v4l2_pix_format_mplane *pix_fmt = &ctx->src_fmt; + struct v4l2_rect *crop = &ctx->src_crop; + unsigned bytes_per_line, overfill_r, overfill_b; + + /* + * The hardware needs only the value for luma plane, because + * values of other planes are calculated internally based on + * format setting. + */ + bytes_per_line = pix_fmt->plane_fmt[0].bytesperline; + overfill_r = (pix_fmt->width - crop->width) / 4; + overfill_b = pix_fmt->height - crop->height; + + return VEPU_REG_IN_IMG_CTRL_ROW_LEN(bytes_per_line) + | VEPU_REG_IN_IMG_CTRL_OVRFLR_D4(overfill_r) + | VEPU_REG_IN_IMG_CTRL_OVRFLB_D4(overfill_b) + | VEPU_REG_IN_IMG_CTRL_FMT(ctx->vpu_src_fmt->enc_fmt); +} + +static void rk3288_vpu_vp8e_set_buffers(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_ctx *ctx) +{ + const struct rk3288_vp8e_reg_params *params = ctx->run.vp8e.reg_params; + dma_addr_t ref_buf_dma, rec_buf_dma; + dma_addr_t stream_dma; + size_t rounded_size; + dma_addr_t dst_dma; + u32 start_offset; + size_t dst_size; + + rounded_size = ref_luma_size(ctx->src_fmt.width, + ctx->src_fmt.height); + + ref_buf_dma = rec_buf_dma = ctx->hw.vp8e.ext_buf.dma; + if (ctx->hw.vp8e.ref_rec_ptr) + ref_buf_dma += rounded_size * 3 / 2; + else + rec_buf_dma += rounded_size * 3 / 2; + ctx->hw.vp8e.ref_rec_ptr ^= 1; + + dst_dma = vb2_dma_contig_plane_dma_addr(&ctx->run.dst->b, 0); + dst_size = vb2_plane_size(&ctx->run.dst->b, 0); + + /* + * stream addr-->| + * align 64bits->|<-start offset->| + * |<---------header size-------->|<---dst buf--- + */ + start_offset = (params->rlc_ctrl & VEPU_REG_RLC_CTRL_STR_OFFS_MASK) + >> VEPU_REG_RLC_CTRL_STR_OFFS_SHIFT; + stream_dma = dst_dma + params->hdr_len; + + /** + * Userspace will pass 8 bytes aligned size(round_down) to us, + * so we need to plus start offset to get real header size. + * + * |<-aligned size->|<-start offset->| + * |<----------header size---------->| + */ + ctx->run.dst->vp8e.hdr_size = params->hdr_len + (start_offset >> 3); + + if (params->enc_ctrl & VEPU_REG_ENC_CTRL_KEYFRAME_BIT) + ctx->run.dst->b.v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; + else + ctx->run.dst->b.v4l2_buf.flags &= ~V4L2_BUF_FLAG_KEYFRAME; + + /* + * We assume here that 1/10 of the buffer is enough for headers. + * DCT partition will be placed in remaining 9/10 of the buffer. + */ + ctx->run.dst->vp8e.dct_offset = round_up(dst_size / 10, 8); + + /* Destination buffer. */ + vepu_write_relaxed(vpu, stream_dma, VEPU_REG_ADDR_OUTPUT_STREAM); + vepu_write_relaxed(vpu, dst_dma + ctx->run.dst->vp8e.dct_offset, + VEPU_REG_ADDR_VP8_DCT_PART(0)); + vepu_write_relaxed(vpu, dst_size - ctx->run.dst->vp8e.dct_offset, + VEPU_REG_STR_BUF_LIMIT); + + /* Auxilliary buffers. */ + vepu_write_relaxed(vpu, ctx->hw.vp8e.ctrl_buf.dma, + VEPU_REG_ADDR_OUTPUT_CTRL); + vepu_write_relaxed(vpu, ctx->hw.vp8e.mv_buf.dma, + VEPU_REG_ADDR_MV_OUT); + vepu_write_relaxed(vpu, ctx->run.priv_dst.dma, + VEPU_REG_ADDR_VP8_PROB_CNT); + vepu_write_relaxed(vpu, ctx->run.priv_src.dma + VP8_CABAC_CTX_OFFSET, + VEPU_REG_ADDR_CABAC_TBL); + vepu_write_relaxed(vpu, ctx->run.priv_src.dma + + VP8_CABAC_CTX_OFFSET + VP8_CABAC_CTX_SIZE, + VEPU_REG_ADDR_VP8_SEG_MAP); + + /* Reference buffers. */ + vepu_write_relaxed(vpu, ref_buf_dma, + VEPU_REG_ADDR_REF_LUMA); + vepu_write_relaxed(vpu, ref_buf_dma + rounded_size, + VEPU_REG_ADDR_REF_CHROMA); + + /* Reconstruction buffers. */ + vepu_write_relaxed(vpu, rec_buf_dma, + VEPU_REG_ADDR_REC_LUMA); + vepu_write_relaxed(vpu, rec_buf_dma + rounded_size, + VEPU_REG_ADDR_REC_CHROMA); + + /* Source buffer. */ + vepu_write_relaxed(vpu, vb2_dma_contig_plane_dma_addr(&ctx->run.src->b, + PLANE_Y), VEPU_REG_ADDR_IN_LUMA); + vepu_write_relaxed(vpu, vb2_dma_contig_plane_dma_addr(&ctx->run.src->b, + PLANE_CB), VEPU_REG_ADDR_IN_CB); + vepu_write_relaxed(vpu, vb2_dma_contig_plane_dma_addr(&ctx->run.src->b, + PLANE_CR), VEPU_REG_ADDR_IN_CR); + + /* Source parameters. */ + vepu_write_relaxed(vpu, enc_in_img_ctrl(ctx), VEPU_REG_IN_IMG_CTRL); +} + +static void rk3288_vpu_vp8e_set_params(struct rk3288_vpu_dev *vpu, + struct rk3288_vpu_ctx *ctx) +{ + const struct rk3288_vp8e_reg_params *params = ctx->run.vp8e.reg_params; + int i; + + vepu_write_relaxed(vpu, params->enc_ctrl0, VEPU_REG_ENC_CTRL0); + vepu_write_relaxed(vpu, params->enc_ctrl1, VEPU_REG_ENC_CTRL1); + vepu_write_relaxed(vpu, params->enc_ctrl2, VEPU_REG_ENC_CTRL2); + vepu_write_relaxed(vpu, params->enc_ctrl3, VEPU_REG_ENC_CTRL3); + vepu_write_relaxed(vpu, params->enc_ctrl5, VEPU_REG_ENC_CTRL5); + vepu_write_relaxed(vpu, params->enc_ctrl4, VEPU_REG_ENC_CTRL4); + vepu_write_relaxed(vpu, params->str_hdr_rem_msb, + VEPU_REG_STR_HDR_REM_MSB); + vepu_write_relaxed(vpu, params->str_hdr_rem_lsb, + VEPU_REG_STR_HDR_REM_LSB); + vepu_write_relaxed(vpu, params->mad_ctrl, VEPU_REG_MAD_CTRL); + + for (i = 0; i < ARRAY_SIZE(params->qp_val); ++i) + vepu_write_relaxed(vpu, params->qp_val[i], + VEPU_REG_VP8_QP_VAL(i)); + + vepu_write_relaxed(vpu, params->bool_enc, VEPU_REG_VP8_BOOL_ENC); + vepu_write_relaxed(vpu, params->vp8_ctrl0, VEPU_REG_VP8_CTRL0); + vepu_write_relaxed(vpu, params->rlc_ctrl, VEPU_REG_RLC_CTRL); + vepu_write_relaxed(vpu, params->mb_ctrl, VEPU_REG_MB_CTRL); + + for (i = 0; i < ARRAY_SIZE(params->rgb_yuv_coeff); ++i) + vepu_write_relaxed(vpu, params->rgb_yuv_coeff[i], + VEPU_REG_RGB_YUV_COEFF(i)); + + vepu_write_relaxed(vpu, params->rgb_mask_msb, + VEPU_REG_RGB_MASK_MSB); + vepu_write_relaxed(vpu, params->intra_area_ctrl, + VEPU_REG_INTRA_AREA_CTRL); + vepu_write_relaxed(vpu, params->cir_intra_ctrl, + VEPU_REG_CIR_INTRA_CTRL); + vepu_write_relaxed(vpu, params->first_roi_area, + VEPU_REG_FIRST_ROI_AREA); + vepu_write_relaxed(vpu, params->second_roi_area, + VEPU_REG_SECOND_ROI_AREA); + vepu_write_relaxed(vpu, params->mvc_ctrl, + VEPU_REG_MVC_CTRL); + + for (i = 0; i < ARRAY_SIZE(params->intra_penalty); ++i) + vepu_write_relaxed(vpu, params->intra_penalty[i], + VEPU_REG_VP8_INTRA_PENALTY(i)); + + for (i = 0; i < ARRAY_SIZE(params->seg_qp); ++i) + vepu_write_relaxed(vpu, params->seg_qp[i], + VEPU_REG_VP8_SEG_QP(i)); + + for (i = 0; i < ARRAY_SIZE(params->dmv_4p_1p_penalty); ++i) + vepu_write_relaxed(vpu, params->dmv_4p_1p_penalty[i], + VEPU_REG_DMV_4P_1P_PENALTY(i)); + + for (i = 0; i < ARRAY_SIZE(params->dmv_qpel_penalty); ++i) + vepu_write_relaxed(vpu, params->dmv_qpel_penalty[i], + VEPU_REG_DMV_QPEL_PENALTY(i)); + + vepu_write_relaxed(vpu, params->vp8_ctrl1, VEPU_REG_VP8_CTRL1); + vepu_write_relaxed(vpu, params->bit_cost_golden, + VEPU_REG_VP8_BIT_COST_GOLDEN); + + for (i = 0; i < ARRAY_SIZE(params->loop_flt_delta); ++i) + vepu_write_relaxed(vpu, params->loop_flt_delta[i], + VEPU_REG_VP8_LOOP_FLT_DELTA(i)); +} + +void rk3288_vpu_vp8e_run(struct rk3288_vpu_ctx *ctx) +{ + struct rk3288_vpu_dev *vpu = ctx->dev; + u32 reg; + + /* The hardware expects the control buffer to be zeroed. */ + memset(ctx->hw.vp8e.ctrl_buf.cpu, 0, + sizeof(struct rk3288_vpu_vp8e_ctrl_buf)); + + /* + * Program the hardware. + */ + rk3288_vpu_power_on(vpu); + + vepu_write_relaxed(vpu, VEPU_REG_ENC_CTRL_ENC_MODE_VP8, + VEPU_REG_ENC_CTRL); + + rk3288_vpu_vp8e_set_params(vpu, ctx); + rk3288_vpu_vp8e_set_buffers(vpu, ctx); + + /* Make sure that all registers are written at this point. */ + wmb(); + + /* Set the watchdog. */ + schedule_delayed_work(&vpu->watchdog_work, msecs_to_jiffies(2000)); + + /* Start the hardware. */ + reg = VEPU_REG_AXI_CTRL_OUTPUT_SWAP16 + | VEPU_REG_AXI_CTRL_INPUT_SWAP16 + | VEPU_REG_AXI_CTRL_BURST_LEN(16) + | VEPU_REG_AXI_CTRL_GATE_BIT + | VEPU_REG_AXI_CTRL_OUTPUT_SWAP32 + | VEPU_REG_AXI_CTRL_INPUT_SWAP32 + | VEPU_REG_AXI_CTRL_OUTPUT_SWAP8 + | VEPU_REG_AXI_CTRL_INPUT_SWAP8; + vepu_write(vpu, reg, VEPU_REG_AXI_CTRL); + + vepu_write(vpu, 0, VEPU_REG_INTERRUPT); + + reg = VEPU_REG_ENC_CTRL_NAL_MODE_BIT + | VEPU_REG_ENC_CTRL_WIDTH(MB_WIDTH(ctx->src_fmt.width)) + | VEPU_REG_ENC_CTRL_HEIGHT(MB_HEIGHT(ctx->src_fmt.height)) + | VEPU_REG_ENC_CTRL_ENC_MODE_VP8 + | VEPU_REG_ENC_CTRL_EN_BIT; + + if (ctx->run.dst->b.v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) + reg |= VEPU_REG_ENC_CTRL_KEYFRAME_BIT; + + vepu_write(vpu, reg, VEPU_REG_ENC_CTRL); +} + +void rk3288_vpu_vp8e_done(struct rk3288_vpu_ctx *ctx, + enum vb2_buffer_state result) +{ + struct rk3288_vpu_vp8e_ctrl_buf *ctrl_buf = ctx->hw.vp8e.ctrl_buf.cpu; + + /* Read length information of this run from utility buffer. */ + ctx->run.dst->vp8e.ext_hdr_size = ctrl_buf->ext_hdr_size; + ctx->run.dst->vp8e.dct_size = ctrl_buf->dct_size; + + rk3288_vpu_run_done(ctx, result); +} diff --git a/drivers/media/platform/rk3288-vpu/rk3288_vpu_regs.h b/drivers/media/platform/rk3288-vpu/rk3288_vpu_regs.h new file mode 100644 index 000000000000..fcdfe69d9899 --- /dev/null +++ b/drivers/media/platform/rk3288-vpu/rk3288_vpu_regs.h @@ -0,0 +1,96 @@ +/* + * Rockchip RK3288 VPU codec driver + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef RK3288_VPU_REGS_H_ +#define RK3288_VPU_REGS_H_ + +/* Encoder registers. */ +#define VEPU_REG_INTERRUPT 0x004 +#define VEPU_REG_INTERRUPT_DIS_BIT BIT(1) +#define VEPU_REG_INTERRUPT_BIT BIT(0) +#define VEPU_REG_AXI_CTRL 0x008 +#define VEPU_REG_AXI_CTRL_OUTPUT_SWAP16 BIT(15) +#define VEPU_REG_AXI_CTRL_INPUT_SWAP16 BIT(14) +#define VEPU_REG_AXI_CTRL_BURST_LEN(x) ((x) << 8) +#define VEPU_REG_AXI_CTRL_GATE_BIT BIT(4) +#define VEPU_REG_AXI_CTRL_OUTPUT_SWAP32 BIT(3) +#define VEPU_REG_AXI_CTRL_INPUT_SWAP32 BIT(2) +#define VEPU_REG_AXI_CTRL_OUTPUT_SWAP8 BIT(1) +#define VEPU_REG_AXI_CTRL_INPUT_SWAP8 BIT(0) +#define VEPU_REG_ADDR_OUTPUT_STREAM 0x014 +#define VEPU_REG_ADDR_OUTPUT_CTRL 0x018 +#define VEPU_REG_ADDR_REF_LUMA 0x01c +#define VEPU_REG_ADDR_REF_CHROMA 0x020 +#define VEPU_REG_ADDR_REC_LUMA 0x024 +#define VEPU_REG_ADDR_REC_CHROMA 0x028 +#define VEPU_REG_ADDR_IN_LUMA 0x02c +#define VEPU_REG_ADDR_IN_CB 0x030 +#define VEPU_REG_ADDR_IN_CR 0x034 +#define VEPU_REG_ENC_CTRL 0x038 +#define VEPU_REG_ENC_CTRL_NAL_MODE_BIT BIT(29) +#define VEPU_REG_ENC_CTRL_WIDTH(w) ((w) << 19) +#define VEPU_REG_ENC_CTRL_HEIGHT(h) ((h) << 10) +#define VEPU_REG_ENC_CTRL_KEYFRAME_BIT BIT(3) +#define VEPU_REG_ENC_CTRL_ENC_MODE_VP8 (0x1 << 1) +#define VEPU_REG_ENC_CTRL_EN_BIT BIT(0) +#define VEPU_REG_IN_IMG_CTRL 0x03c +#define VEPU_REG_IN_IMG_CTRL_ROW_LEN(x) ((x) << 12) +#define VEPU_REG_IN_IMG_CTRL_OVRFLR_D4(x) ((x) << 10) +#define VEPU_REG_IN_IMG_CTRL_OVRFLB_D4(x) ((x) << 6) +#define VEPU_REG_IN_IMG_CTRL_FMT(x) ((x) << 2) +#define VEPU_REG_ENC_CTRL0 0x040 +#define VEPU_REG_ENC_CTRL1 0x044 +#define VEPU_REG_ENC_CTRL2 0x048 +#define VEPU_REG_ENC_CTRL3 0x04c +#define VEPU_REG_ENC_CTRL5 0x050 +#define VEPU_REG_ENC_CTRL4 0x054 +#define VEPU_REG_STR_HDR_REM_MSB 0x058 +#define VEPU_REG_STR_HDR_REM_LSB 0x05c +#define VEPU_REG_STR_BUF_LIMIT 0x060 +#define VEPU_REG_MAD_CTRL 0x064 +#define VEPU_REG_ADDR_VP8_PROB_CNT 0x068 +#define VEPU_REG_QP_VAL 0x06c +#define VEPU_REG_VP8_QP_VAL(i) (0x06c + ((i) * 0x4)) +#define VEPU_REG_CHECKPOINT(i) (0x070 + ((i) * 0x4)) +#define VEPU_REG_CHKPT_WORD_ERR(i) (0x084 + ((i) * 0x4)) +#define VEPU_REG_VP8_BOOL_ENC 0x08c +#define VEPU_REG_CHKPT_DELTA_QP 0x090 +#define VEPU_REG_VP8_CTRL0 0x090 +#define VEPU_REG_RLC_CTRL 0x094 +#define VEPU_REG_RLC_CTRL_STR_OFFS_SHIFT 23 +#define VEPU_REG_RLC_CTRL_STR_OFFS_MASK (0x3f << 23) +#define VEPU_REG_MB_CTRL 0x098 +#define VEPU_REG_ADDR_CABAC_TBL 0x0cc +#define VEPU_REG_ADDR_MV_OUT 0x0d0 +#define VEPU_REG_RGB_YUV_COEFF(i) (0x0d4 + ((i) * 0x4)) +#define VEPU_REG_RGB_MASK_MSB 0x0dc +#define VEPU_REG_INTRA_AREA_CTRL 0x0e0 +#define VEPU_REG_CIR_INTRA_CTRL 0x0e4 +#define VEPU_REG_INTRA_SLICE_BITMAP(i) (0x0e8 + ((i) * 0x4)) +#define VEPU_REG_ADDR_VP8_DCT_PART(i) (0x0e8 + ((i) * 0x4)) +#define VEPU_REG_FIRST_ROI_AREA 0x0f0 +#define VEPU_REG_SECOND_ROI_AREA 0x0f4 +#define VEPU_REG_MVC_CTRL 0x0f8 +#define VEPU_REG_VP8_INTRA_PENALTY(i) (0x100 + ((i) * 0x4)) +#define VEPU_REG_ADDR_VP8_SEG_MAP 0x11c +#define VEPU_REG_VP8_SEG_QP(i) (0x120 + ((i) * 0x4)) +#define VEPU_REG_DMV_4P_1P_PENALTY(i) (0x180 + ((i) * 0x4)) +#define VEPU_REG_DMV_QPEL_PENALTY(i) (0x200 + ((i) * 0x4)) +#define VEPU_REG_VP8_CTRL1 0x280 +#define VEPU_REG_VP8_BIT_COST_GOLDEN 0x284 +#define VEPU_REG_VP8_LOOP_FLT_DELTA(i) (0x288 + ((i) * 0x4)) + +#endif /* RK3288_VPU_REGS_H_ */