/* * Rockchip 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 "rockchip_vpu_common.h" #include #include #include #include #include #include #include #include #include #include #include "rockchip_vpu_dec.h" #include "rockchip_vpu_enc.h" #include "rockchip_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 rockchip_vpu_aux_buf_alloc(struct rockchip_vpu_dev *vpu, struct rockchip_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 rockchip_vpu_aux_buf_free(struct rockchip_vpu_dev *vpu, struct rockchip_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 rockchip_vpu_prepare_run(struct rockchip_vpu_ctx *ctx) { if (ctx->run_ops->prepare_run) ctx->run_ops->prepare_run(ctx); } static void __rockchip_vpu_dequeue_run_locked(struct rockchip_vpu_ctx *ctx) { struct rockchip_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 rockchip_vpu_buf, list); dst = list_first_entry(&ctx->dst_queue, struct rockchip_vpu_buf, list); list_del(&src->list); list_del(&dst->list); ctx->run.src = src; ctx->run.dst = dst; } static struct rockchip_vpu_ctx * rockchip_vpu_encode_after_decode_war(struct rockchip_vpu_ctx *ctx) { struct rockchip_vpu_dev *dev = ctx->dev; if (dev->dummy_encode_ctx && dev->was_decoding && rockchip_vpu_ctx_is_encoder(ctx)) return dev->dummy_encode_ctx; return ctx; } static void rockchip_vpu_try_run(struct rockchip_vpu_dev *dev) { struct rockchip_vpu_ctx *ctx = NULL; unsigned long flags; vpu_debug_enter(); spin_lock_irqsave(&dev->irqlock, flags); if (list_empty(&dev->ready_ctxs) || test_bit(VPU_SUSPENDED, &dev->state)) /* 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 rockchip_vpu_run_done(). */ goto out; ctx = list_entry(dev->ready_ctxs.next, struct rockchip_vpu_ctx, list); /* * WAR for corrupted hardware state when encoding directly after * certain decoding runs. * * If previous context was decoding and currently picked one is * encoding then we need to execute a dummy encode with proper * settings to reinitialize certain internal hardware state. */ ctx = rockchip_vpu_encode_after_decode_war(ctx); if (!rockchip_vpu_ctx_is_dummy_encode(ctx)) { list_del_init(&ctx->list); __rockchip_vpu_dequeue_run_locked(ctx); } dev->current_ctx = ctx; dev->was_decoding = !rockchip_vpu_ctx_is_encoder(ctx); out: spin_unlock_irqrestore(&dev->irqlock, flags); if (ctx) { rockchip_vpu_prepare_run(ctx); rockchip_vpu_run(ctx); } vpu_debug_leave(); } static void __rockchip_vpu_try_context_locked(struct rockchip_vpu_dev *dev, struct rockchip_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 rockchip_vpu_run_done(struct rockchip_vpu_ctx *ctx, enum vb2_buffer_state result) { struct rockchip_vpu_dev *dev = ctx->dev; unsigned long flags; vpu_debug_enter(); if (ctx->run_ops->run_done) ctx->run_ops->run_done(ctx, result); if (!rockchip_vpu_ctx_is_dummy_encode(ctx)) { struct vb2_v4l2_buffer *src = to_vb2_v4l2_buffer(&ctx->run.src->vb.vb2_buf); struct vb2_v4l2_buffer *dst = to_vb2_v4l2_buffer(&ctx->run.dst->vb.vb2_buf); dst->timestamp = src->timestamp; vb2_buffer_done(&ctx->run.src->vb.vb2_buf, result); vb2_buffer_done(&ctx->run.dst->vb.vb2_buf, result); } dev->current_ctx = NULL; wake_up_all(&dev->run_wq); spin_lock_irqsave(&dev->irqlock, flags); __rockchip_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. */ rockchip_vpu_try_run(dev); vpu_debug_leave(); } void rockchip_vpu_try_context(struct rockchip_vpu_dev *dev, struct rockchip_vpu_ctx *ctx) { unsigned long flags; vpu_debug_enter(); spin_lock_irqsave(&dev->irqlock, flags); __rockchip_vpu_try_context_locked(dev, ctx); spin_unlock_irqrestore(&dev->irqlock, flags); rockchip_vpu_try_run(dev); vpu_debug_enter(); } /* * bit stream assembler */ static int stream_buffer_status(struct stream_s *stream) { if (stream->byte_cnt + 5 > stream->size) { stream->overflow = 1; return -1; } return 0; } void stream_put_bits(struct stream_s *buffer, s32 value, s32 number, const char *name) { s32 bits; u32 byte_buffer = buffer->byte_buffer; u8 *stream = buffer->stream; if (stream_buffer_status(buffer) != 0) return; vpu_debug(0, "assemble %s value %x, bits %d\n", name, value, number); BUG_ON(value >= (1 << number)); BUG_ON(number >= 25); bits = number + buffer->buffered_bits; value <<= (32 - bits); byte_buffer = byte_buffer | value; while (bits > 7) { *stream = (u8)(byte_buffer >> 24); bits -= 8; byte_buffer <<= 8; stream++; buffer->byte_cnt++; } buffer->byte_buffer = byte_buffer; buffer->buffered_bits = (u8)bits; buffer->stream = stream; return; } void stream_buffer_reset(struct stream_s *buffer) { buffer->stream = buffer->buffer; buffer->byte_cnt = 0; buffer->overflow = 0; buffer->byte_buffer = 0; buffer->buffered_bits = 0; } int stream_buffer_init(struct stream_s *buffer, u8 *stream, s32 size) { if (stream == NULL) { buffer->stream = kzalloc(size, GFP_KERNEL); } if (buffer->stream == NULL) { vpu_err("allocate stream buffer failed\n"); return -1; } buffer->buffer = buffer->stream; buffer->size = size; stream_buffer_reset(buffer); if (stream_buffer_status(buffer) != 0) return -1; return 0; } /* * Control registration. */ #define IS_VPU_PRIV(x) ((V4L2_CTRL_ID2WHICH(x) == V4L2_CTRL_CLASS_MPEG) && \ V4L2_CTRL_DRIVER_PRIV(x)) int rockchip_vpu_ctrls_setup(struct rockchip_vpu_ctx *ctx, const struct v4l2_ctrl_ops *ctrl_ops, struct rockchip_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 rockchip_vpu_ctrls_delete(struct rockchip_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 rockchip_vpu_open(struct file *filp) { struct video_device *vdev = video_devdata(filp); struct rockchip_vpu_dev *dev = video_drvdata(filp); struct rockchip_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 = rockchip_vpu_enc_init(ctx); if (ret) { vpu_err("Failed to initialize encoder context\n"); goto err_fh_free; } } else if (vdev == dev->vfd_dec) { /* only for decoder */ ret = rockchip_vpu_dec_init(ctx); if (ret) { vpu_err("Failed to initialize decoder 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 | VB2_DMABUF; q->lock = &dev->vpu_mutex; q->buf_struct_size = sizeof(struct rockchip_vpu_buf); if (vdev == dev->vfd_enc) { q->ops = get_enc_queue_ops(); } else if (vdev == dev->vfd_dec) { q->ops = get_dec_queue_ops(); q->use_dma_bidirectional = 1; } q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = 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 | VB2_DMABUF; q->lock = &dev->vpu_mutex; q->buf_struct_size = sizeof(struct rockchip_vpu_buf); if (vdev == dev->vfd_enc) q->ops = get_enc_queue_ops(); else if (vdev == dev->vfd_dec) q->ops = get_dec_queue_ops(); q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = 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) rockchip_vpu_enc_exit(ctx); else if (vdev == dev->vfd_dec) rockchip_vpu_dec_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 rockchip_vpu_release(struct file *filp) { struct rockchip_vpu_ctx *ctx = fh_to_ctx(filp->private_data); struct video_device *vdev = video_devdata(filp); struct rockchip_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) rockchip_vpu_enc_exit(ctx); else if (vdev == dev->vfd_dec) rockchip_vpu_dec_exit(ctx); kfree(ctx); vpu_debug_leave(); return 0; } static unsigned int rockchip_vpu_poll(struct file *filp, struct poll_table_struct *wait) { struct rockchip_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 rockchip_vpu_mmap(struct file *filp, struct vm_area_struct *vma) { struct rockchip_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 rockchip_vpu_fops = { .owner = THIS_MODULE, .open = rockchip_vpu_open, .release = rockchip_vpu_release, .poll = rockchip_vpu_poll, .unlocked_ioctl = video_ioctl2, .mmap = rockchip_vpu_mmap, }; /* * Platform driver. */ static void* rockchip_get_drv_data(struct platform_device *pdev); static int rockchip_vpu_probe(struct platform_device *pdev) { struct rockchip_vpu_dev *vpu = NULL; DEFINE_DMA_ATTRS(attrs_novm); DEFINE_DMA_ATTRS(attrs_nohugepage); 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); vpu->variant = rockchip_get_drv_data(pdev); ret = rockchip_vpu_hw_probe(vpu); if (ret) { dev_err(&pdev->dev, "rockchip_vpu_hw_probe failed\n"); goto err_hw_probe; } /* * We'll do mostly sequential access, so sacrifice TLB efficiency for * faster allocation. */ dma_set_attr(DMA_ATTR_ALLOC_SINGLE_PAGES, &attrs_novm); dma_set_attr(DMA_ATTR_NO_KERNEL_MAPPING, &attrs_novm); vpu->alloc_ctx = vb2_dma_contig_init_ctx_attrs(&pdev->dev, &attrs_novm); if (IS_ERR(vpu->alloc_ctx)) { ret = PTR_ERR(vpu->alloc_ctx); goto err_dma_contig; } dma_set_attr(DMA_ATTR_ALLOC_SINGLE_PAGES, &attrs_nohugepage); vpu->alloc_ctx_vm = vb2_dma_contig_init_ctx_attrs(&pdev->dev, &attrs_nohugepage); if (IS_ERR(vpu->alloc_ctx_vm)) { ret = PTR_ERR(vpu->alloc_ctx_vm); goto err_dma_contig_vm; } 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); /* workaround for rk3288 codecs */ if (vpu->variant->codecs == RK3288_CODECS) { ret = rockchip_vpu_enc_init_dummy_ctx(vpu); if (ret) { dev_err(&pdev->dev, "Failed to create dummy encode context\n"); goto err_dummy_enc; } } /* encoder */ if (!(vpu->variant->codecs & ROCKCHIP_VPU_ENCODERS)) goto no_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 = &rockchip_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", ROCKCHIP_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 VPU encoder registered as /vpu/video%d\n", vfd->num); no_encoder: /* decoder */ if (!(vpu->variant->codecs & ROCKCHIP_VPU_DECODERS)) goto no_decoder; vfd = video_device_alloc(); if (!vfd) { v4l2_err(&vpu->v4l2_dev, "Failed to allocate video device\n"); ret = -ENOMEM; goto err_dec_alloc; } vfd->fops = &rockchip_vpu_fops; vfd->ioctl_ops = get_dec_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", ROCKCHIP_VPU_DEC_NAME); vpu->vfd_dec = 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_dec_reg; } v4l2_info(&vpu->v4l2_dev, "Rockchip VPU decoder registered as /vpu/video%d\n", vfd->num); no_decoder: vpu_debug_leave(); return 0; err_dec_reg: video_device_release(vpu->vfd_dec); err_dec_alloc: video_unregister_device(vpu->vfd_enc); err_enc_reg: video_device_release(vpu->vfd_enc); err_enc_alloc: rockchip_vpu_enc_free_dummy_ctx(vpu); err_dummy_enc: v4l2_device_unregister(&vpu->v4l2_dev); err_v4l2_dev_reg: vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx_vm); err_dma_contig_vm: vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx); err_dma_contig: rockchip_vpu_hw_remove(vpu); err_hw_probe: pr_debug("%s-- with error\n", __func__); vpu_debug_leave(); return ret; } static int rockchip_vpu_remove(struct platform_device *pdev) { struct rockchip_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_dec); video_unregister_device(vpu->vfd_enc); rockchip_vpu_enc_free_dummy_ctx(vpu); v4l2_device_unregister(&vpu->v4l2_dev); vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx_vm); vb2_dma_contig_cleanup_ctx(vpu->alloc_ctx); rockchip_vpu_hw_remove(vpu); vpu_debug_leave(); return 0; } /* Supported VPU variants. */ static const struct rockchip_vpu_variant rk3288_vpu_variant = { .name = "Rk3288 vpu", .codecs = RK3288_CODECS, .enc_offset = 0x0, .enc_reg_num = 164, .dec_offset = 0x400, .dec_reg_num = 60 + 41, }; static struct platform_device_id vpu_driver_ids[] = { { .name = "rk3288-vpu", .driver_data = (unsigned long)&rk3288_vpu_variant, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(platform, vpu_driver_ids); #ifdef CONFIG_OF static const struct of_device_id of_rockchip_vpu_match[] = { { .compatible = "rockchip,rk3288-vpu", .data = &rk3288_vpu_variant, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, of_rockchip_vpu_match); #endif static void* rockchip_get_drv_data(struct platform_device *pdev) { #ifdef CONFIG_OF if (pdev->dev.of_node) { const struct of_device_id *match; match = of_match_node(of_rockchip_vpu_match, pdev->dev.of_node); if (match) return (void *)match->data; } #endif return (void *)platform_get_device_id(pdev)->driver_data; } #ifdef CONFIG_PM_SLEEP static int rockchip_vpu_suspend(struct device *dev) { struct rockchip_vpu_dev *vpu = dev_get_drvdata(dev); set_bit(VPU_SUSPENDED, &vpu->state); wait_event(vpu->run_wq, vpu->current_ctx == NULL); return 0; } static int rockchip_vpu_resume(struct device *dev) { struct rockchip_vpu_dev *vpu = dev_get_drvdata(dev); clear_bit(VPU_SUSPENDED, &vpu->state); rockchip_vpu_try_run(vpu); return 0; } #endif static const struct dev_pm_ops rockchip_vpu_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(rockchip_vpu_suspend, rockchip_vpu_resume) }; static struct platform_driver rockchip_vpu_driver = { .probe = rockchip_vpu_probe, .remove = rockchip_vpu_remove, .id_table = vpu_driver_ids, .driver = { .name = ROCKCHIP_VPU_NAME, .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = of_match_ptr(of_rockchip_vpu_match), #endif .pm = &rockchip_vpu_pm_ops, }, }; module_platform_driver(rockchip_vpu_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Alpha Lin "); MODULE_AUTHOR("Tomasz Figa "); MODULE_DESCRIPTION("Rockchip VPU codec driver");