/* * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. * Copyright (c) 2009, Google Inc. * * Original authors: Code Aurora Forum * Major cleanup: Dima Zavin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Code Aurora Forum nor * the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ //#define DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dal.h" #define DALDEVICEID_VENC_DEVICE 0x0200002D #define DALDEVICEID_VENC_PORTNAME "DSP_DAL_AQ_VID" enum { VENC_DALRPC_INITIALIZE = DAL_OP_FIRST_DEVICE_API, VENC_DALRPC_SET_CB_CHANNEL, VENC_DALRPC_ENCODE, VENC_DALRPC_INTRA_REFRESH, VENC_DALRPC_RC_CONFIG, VENC_DALRPC_ENCODE_CONFIG, VENC_DALRPC_STOP, }; struct callback_event_data { u32 data_notify_event; u32 enc_cb_handle; u32 empty_input_buffer_event; }; struct buf_info { unsigned long paddr; unsigned long vaddr; struct file *file; struct venc_buf venc_buf; }; #define VENC_MAX_BUF_NUM 15 #define RLC_MAX_BUF_NUM 2 #define BITS_PER_PIXEL 12 #define PIXELS_PER_MACROBLOCK 16 #define VENC_CB_EVENT_ID 0xd0e4c0de struct q6venc_dev { struct dal_client *venc; struct callback_event_data cb_ev_data; bool stop_encode; struct buf_info rlc_bufs[RLC_MAX_BUF_NUM]; unsigned int rlc_buf_index; unsigned int rlc_buf_len; unsigned int enc_buf_size; struct buf_info enc_bufs[VENC_MAX_BUF_NUM]; unsigned int num_enc_bufs; wait_queue_head_t encode_wq; /* protects all state in q6venc_dev except for cb stuff below */ struct mutex lock; /* protects encode_done and done_frame inside the callback */ spinlock_t done_lock; struct frame_type done_frame; bool encode_done; }; static int get_buf_info(struct buf_info *buf_info, struct venc_buf *venc_buf) { unsigned long len; unsigned long vaddr; unsigned long paddr; struct file *file; int ret; ret = get_pmem_file(venc_buf->fd, &paddr, &vaddr, &len, &file); if (ret) { pr_err("%s: get_pmem_file failed for fd=%d offset=%ld\n", __func__, venc_buf->fd, venc_buf->offset); return ret; } else if (venc_buf->offset >= len) { /* XXX: we really should check venc_buf->size too, but userspace * sometimes leaves this uninitialized (in encode ioctl) */ pr_err("%s: invalid offset/size (%ld + %ld > %ld) for fd=%d\n", __func__, venc_buf->offset, venc_buf->size, len, venc_buf->fd); put_pmem_file(file); return -EINVAL; } buf_info->file = file; buf_info->paddr = paddr + venc_buf->offset; buf_info->vaddr = vaddr; memcpy(&buf_info->venc_buf, venc_buf, sizeof(struct venc_buf)); return 0; } static void put_buf_info(struct buf_info *buf_info) { if (!buf_info || !buf_info->file) return; put_pmem_file(buf_info->file); buf_info->file = NULL; } static void q6venc_callback(void *context, void *data, uint32_t len) { struct q6venc_dev *q6venc = context; struct q6_frame_type *q6frame = data; struct buf_info *rlc_buf; unsigned long flags; int i; pr_debug("%s \n", __func__); spin_lock_irqsave(&q6venc->done_lock, flags); q6venc->encode_done = true; for (i = 0; i < RLC_MAX_BUF_NUM; ++i) { rlc_buf = &q6venc->rlc_bufs[i]; if (rlc_buf->paddr == q6frame->frame_addr) goto frame_found; } pr_err("%s: got incorrect phy address 0x%08x from q6 \n", __func__, q6frame->frame_addr); q6venc->done_frame.q6_frame_type.frame_len = 0; wake_up_interruptible(&q6venc->encode_wq); goto done; frame_found: memcpy(&q6venc->done_frame.frame_addr, &rlc_buf->venc_buf, sizeof(struct venc_buf)); memcpy(&q6venc->done_frame.q6_frame_type, q6frame, sizeof(struct q6_frame_type)); dmac_inv_range((const void *)q6venc->rlc_bufs[i].vaddr, (const void *)(q6venc->rlc_bufs[i].vaddr + q6venc->rlc_buf_len)); wake_up_interruptible(&q6venc->encode_wq); done: spin_unlock_irqrestore(&q6venc->done_lock, flags); } static void callback(void *data, int len, void *cookie) { struct q6venc_dev *ve = (struct q6venc_dev *)cookie; uint32_t *tmp = (uint32_t *) data; if (tmp[0] == VENC_CB_EVENT_ID) q6venc_callback(ve, &tmp[3], tmp[2]); else pr_err("%s: Unknown callback received for %p\n", __func__, ve); } static int q6venc_open(struct inode *inode, struct file *file) { struct q6venc_dev *q6venc; int err; q6venc = kzalloc(sizeof(struct q6venc_dev), GFP_KERNEL); if (!q6venc) { pr_err("%s: Unable to allocate memory for q6venc_dev\n", __func__); return -ENOMEM; } file->private_data = q6venc; init_waitqueue_head(&q6venc->encode_wq); mutex_init(&q6venc->lock); spin_lock_init(&q6venc->done_lock); q6venc->venc = dal_attach(DALDEVICEID_VENC_DEVICE, DALDEVICEID_VENC_PORTNAME, callback, q6venc); if (!q6venc->venc) { pr_err("%s: dal_attach failed\n", __func__); err = -EIO; goto err_dal_attach; } q6venc->cb_ev_data.enc_cb_handle = VENC_CB_EVENT_ID; err = dal_call_f5(q6venc->venc, VENC_DALRPC_SET_CB_CHANNEL, &q6venc->cb_ev_data, sizeof(q6venc->cb_ev_data)); if (err) { pr_err("%s: set_cb_channgel failed\n", __func__); goto err_dal_call_set_cb; } pr_info("%s() handle=%p enc_cb=%08x\n", __func__, q6venc->venc, q6venc->cb_ev_data.enc_cb_handle); return 0; err_dal_call_set_cb: dal_detach(q6venc->venc); err_dal_attach: file->private_data = NULL; mutex_destroy(&q6venc->lock); kfree(q6venc); return err; } static int q6venc_release(struct inode *inode, struct file *file) { struct q6venc_dev *q6venc; int id, err; q6venc = file->private_data; file->private_data = NULL; pr_info("q6venc_close() handle=%p\n", q6venc->venc); for (id = 0; id < q6venc->num_enc_bufs; id++) put_buf_info(&q6venc->enc_bufs[id]); put_buf_info(&q6venc->rlc_bufs[0]); put_buf_info(&q6venc->rlc_bufs[1]); if(!q6venc->stop_encode) { err = dal_call_f0(q6venc->venc, VENC_DALRPC_STOP, 1); if (err) pr_err("%s: dal_rpc STOP call failed\n", __func__); q6venc->stop_encode = true; } dal_detach(q6venc->venc); mutex_destroy(&q6venc->lock); kfree(q6venc); return 0; } static int q6_config_encode(struct q6venc_dev *q6venc, uint32_t type, struct init_config *init_config) { struct q6_init_config *q6_init_config = &init_config->q6_init_config; int ret; int i; mutex_lock(&q6venc->lock); if (q6venc->num_enc_bufs != 0) { pr_err("%s: multiple sessions not supported\n", __func__); ret = -EBUSY; goto err_busy; } ret = get_buf_info(&q6venc->enc_bufs[0], &init_config->ref_frame_buf1); if (ret) { pr_err("%s: can't get ref_frame_buf1\n", __func__); goto err_get_ref_frame_buf1; } ret = get_buf_info(&q6venc->enc_bufs[1], &init_config->ref_frame_buf2); if (ret) { pr_err("%s: can't get ref_frame_buf2\n", __func__); goto err_get_ref_frame_buf2; } ret = get_buf_info(&q6venc->rlc_bufs[0], &init_config->rlc_buf1); if (ret) { pr_err("%s: can't get rlc_buf1\n", __func__); goto err_get_rlc_buf1; } ret = get_buf_info(&q6venc->rlc_bufs[1], &init_config->rlc_buf2); if (ret) { pr_err("%s: can't get rlc_buf2\n", __func__); goto err_get_rlc_buf2; } q6venc->rlc_buf_len = 2 * q6_init_config->rlc_buf_length; q6venc->num_enc_bufs = 2; q6venc->enc_buf_size = (q6_init_config->enc_frame_width_inmb * PIXELS_PER_MACROBLOCK) * (q6_init_config->enc_frame_height_inmb * PIXELS_PER_MACROBLOCK) * BITS_PER_PIXEL / 8; q6_init_config->ref_frame_buf1_phy = q6venc->enc_bufs[0].paddr; q6_init_config->ref_frame_buf2_phy = q6venc->enc_bufs[1].paddr; q6_init_config->rlc_buf1_phy = q6venc->rlc_bufs[0].paddr; q6_init_config->rlc_buf2_phy = q6venc->rlc_bufs[1].paddr; // The DSP may use the rlc_bufs during initialization, for (i=0; irlc_bufs[i].vaddr, (const void *)(q6venc->rlc_bufs[i].vaddr + q6venc->rlc_buf_len)); } ret = dal_call_f5(q6venc->venc, type, q6_init_config, sizeof(struct q6_init_config)); if (ret) { pr_err("%s: rpc failed \n", __func__); goto err_dal_rpc_init; } mutex_unlock(&q6venc->lock); return 0; err_dal_rpc_init: q6venc->num_enc_bufs = 0; put_pmem_file(q6venc->rlc_bufs[1].file); err_get_rlc_buf2: put_pmem_file(q6venc->rlc_bufs[0].file); err_get_rlc_buf1: put_pmem_file(q6venc->enc_bufs[1].file); err_get_ref_frame_buf2: put_pmem_file(q6venc->enc_bufs[0].file); err_get_ref_frame_buf1: err_busy: mutex_unlock(&q6venc->lock); return ret; } static int q6_encode(struct q6venc_dev *q6venc, struct encode_param *enc_param) { struct q6_encode_param *q6_param = &enc_param->q6_encode_param; struct file *file; struct buf_info *buf; int i; int ret; int rlc_buf_index; pr_debug("y_addr fd=%d offset=0x%08lx uv_offset=0x%08lx\n", enc_param->y_addr.fd, enc_param->y_addr.offset, enc_param->uv_offset); file = fget(enc_param->y_addr.fd); if (!file) { pr_err("%s: invalid encode buffer fd %d\n", __func__, enc_param->y_addr.fd); return -EBADF; } mutex_lock(&q6venc->lock); for (i = 0; i < q6venc->num_enc_bufs; i++) { buf = &q6venc->enc_bufs[i]; if (buf->file == file && buf->venc_buf.offset == enc_param->y_addr.offset) break; } if (i == q6venc->num_enc_bufs) { if (q6venc->num_enc_bufs == VENC_MAX_BUF_NUM) { pr_err("%s: too many input buffers\n", __func__); ret = -ENOMEM; goto done; } buf = &q6venc->enc_bufs[q6venc->num_enc_bufs]; ret = get_buf_info(buf, &enc_param->y_addr); if (ret) { pr_err("%s: can't get encode buffer\n", __func__); ret = -EINVAL; goto done; } if (!IS_ALIGNED(buf->paddr, PAGE_SIZE)) { pr_err("%s: input buffer not 4k aligned\n", __func__); put_buf_info(buf); ret = -EINVAL; goto done; } q6venc->num_enc_bufs++; } /* We must invalidate the buffer that the DSP will write to * to ensure that a dirty cache line doesn't get flushed on * top of the data that the DSP is writing. * Unfortunately, we have to predict which rlc_buf index the * DSP is going to write to. We assume it will write to buf * 0 the first time we call q6_encode, and alternate afterwards * */ rlc_buf_index = q6venc->rlc_buf_index; dmac_inv_range((const void *)q6venc->rlc_bufs[rlc_buf_index].vaddr, (const void *)(q6venc->rlc_bufs[rlc_buf_index].vaddr + q6venc->rlc_buf_len)); q6venc->rlc_buf_index = (q6venc->rlc_buf_index + 1) % RLC_MAX_BUF_NUM; q6_param->luma_addr = buf->paddr; q6_param->chroma_addr = q6_param->luma_addr + enc_param->uv_offset; pr_debug("luma_addr=0x%08x chroma_addr=0x%08x\n", q6_param->luma_addr, q6_param->chroma_addr); /* Ideally, each ioctl that passed in a data buffer would include the size * of the input buffer, so we can properly flush the cache on it. Since * userspace does not fill in the size fields, we have to assume the size * based on the encoder configuration for now. */ flush_pmem_file(buf->file, enc_param->y_addr.offset, q6venc->enc_buf_size); ret = dal_call_f5(q6venc->venc, VENC_DALRPC_ENCODE, q6_param, sizeof(struct q6_encode_param)); if (ret) { pr_err("%s: encode rpc failed\n", __func__); goto done; } ret = 0; done: mutex_unlock(&q6venc->lock); fput(file); return ret; } static int q6venc_ioctl(struct inode *inode, struct file *file, unsigned cmd, unsigned long arg) { struct q6venc_dev *q6venc = file->private_data; struct init_config config; struct encode_param encode_param; struct intra_refresh intra_refresh; struct rc_config rc_config; struct frame_type frame_done; unsigned int id; unsigned long flags; int err = 0; if (!q6venc) { pr_err("%s: file has no private data\n", __func__); return -ENODEV; } pr_debug("%s\n", __func__); switch (cmd) { case VENC_IOCTL_INITIALIZE: pr_debug("%s: VENC_IOCTL_INITIALIZE\n", __func__); if (copy_from_user(&config, (void __user *)arg, sizeof(config))) return -EFAULT; err = q6_config_encode(q6venc, VENC_DALRPC_INITIALIZE, &config); break; case VENC_IOCTL_ENCODE_CONFIG: pr_debug("%s: VENC_IOCTL_ENCODE_CONFIG\n", __func__); if (copy_from_user(&config, (void __user *)arg, sizeof(config))) return -EFAULT; err = q6_config_encode(q6venc, VENC_DALRPC_ENCODE_CONFIG, &config); break; case VENC_IOCTL_ENCODE: pr_debug("%s: VENC_IOCTL_ENCODE\n", __func__); if (copy_from_user(&encode_param, (void __user *)arg, sizeof(encode_param))) return -EFAULT; err = q6_encode(q6venc, &encode_param); break; case VENC_IOCTL_INTRA_REFRESH: pr_debug("%s: VENC_IOCTL_INTRA_REFRESH\n", __func__); if (copy_from_user(&intra_refresh, (void __user *)arg, sizeof(intra_refresh))) return -EFAULT; mutex_lock(&q6venc->lock); err = dal_call_f5(q6venc->venc, VENC_DALRPC_INTRA_REFRESH, &intra_refresh, sizeof(struct intra_refresh)); mutex_unlock(&q6venc->lock); if (err) pr_err("%s: intra_refresh rpc failed\n", __func__); break; case VENC_IOCTL_RC_CONFIG: pr_debug("%s: VENC_IOCTL_RC_CONFIG\n", __func__); if (copy_from_user(&rc_config, (void __user *)arg, sizeof(rc_config))) return -EFAULT; mutex_lock(&q6venc->lock); err = dal_call_f5(q6venc->venc, VENC_DALRPC_RC_CONFIG, &rc_config, sizeof(rc_config)); mutex_unlock(&q6venc->lock); if (err) pr_err("%s: dal_call_f5 failed\n", __func__); break; case VENC_IOCTL_STOP: pr_debug("%s: VENC_IOCTL_STOP\n", __func__); mutex_lock(&q6venc->lock); err = dal_call_f0(q6venc->venc, VENC_DALRPC_STOP, 1); if (err) pr_err("%s: dal_rpc STOP call failed\n", __func__); /* XXX: if the dal call fails we still want to continue to free * the buffers. Is this correct? */ for (id = 0; id < q6venc->num_enc_bufs; id++) put_buf_info(&q6venc->enc_bufs[id]); put_buf_info(&q6venc->rlc_bufs[0]); put_buf_info(&q6venc->rlc_bufs[1]); q6venc->num_enc_bufs = 0; q6venc->stop_encode = true; mutex_unlock(&q6venc->lock); break; case VENC_IOCTL_WAIT_FOR_ENCODE: pr_debug("%s: waiting for encode done event \n", __func__); err = wait_event_interruptible(q6venc->encode_wq, (q6venc->encode_done || q6venc->stop_encode)); if (err < 0) { err = -ERESTARTSYS; break; } mutex_lock(&q6venc->lock); if (q6venc->stop_encode) { q6venc->stop_encode = false; mutex_unlock(&q6venc->lock); pr_debug("%s: Received Stop encode event \n", __func__); err = -EINTR; break; } spin_lock_irqsave(&q6venc->done_lock, flags); if (!q6venc->encode_done) { spin_unlock_irqrestore(&q6venc->done_lock, flags); pr_err("%s: encoding not stopped, and is not done.\n", __func__); err = -EIO; break; } memcpy(&frame_done, &q6venc->done_frame, sizeof(struct frame_type)); q6venc->encode_done = false; spin_unlock_irqrestore(&q6venc->done_lock, flags); mutex_unlock(&q6venc->lock); if (frame_done.q6_frame_type.frame_len == 0) { pr_debug("%s: got incorrect address from q6\n", __func__); err = -EIO; break; } pr_debug("%s: done encoding \n", __func__); if (copy_to_user((void __user *)arg, &frame_done, sizeof(struct frame_type))) err = -EFAULT; break; case VENC_IOCTL_STOP_ENCODE: pr_debug("%s: Stop encode event \n", __func__); mutex_lock(&q6venc->lock); q6venc->stop_encode = true; wake_up_interruptible(&q6venc->encode_wq); mutex_unlock(&q6venc->lock); break; default: err = -ENOTTY; break; } return err; } static const struct file_operations q6venc_dev_fops = { .owner = THIS_MODULE, .open = q6venc_open, .release = q6venc_release, .ioctl = q6venc_ioctl, }; static struct miscdevice q6venc_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "q6venc", .fops = &q6venc_dev_fops, }; static int __init q6venc_init(void) { int rc = 0; rc = misc_register(&q6venc_misc); if (rc) pr_err("%s: Unable to register q6venc misc device\n", __func__); return rc; } static void __exit q6venc_exit(void) { misc_deregister(&q6venc_misc); } MODULE_DESCRIPTION("video encoder driver for QSD platform"); MODULE_VERSION("2.0"); module_init(q6venc_init); module_exit(q6venc_exit);