| /* Copyright (c) 2012, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/bitmap.h> |
| #include <linux/completion.h> |
| #include <linux/ion.h> |
| #include <linux/kthread.h> |
| #include <linux/list.h> |
| #include <linux/mutex.h> |
| #include <linux/wait.h> |
| #include <mach/iommu_domains.h> |
| #include <media/msm_vidc.h> |
| #include <media/v4l2-subdev.h> |
| #include "enc-subdev.h" |
| #include "wfd-util.h" |
| |
| #define BUF_TYPE_OUTPUT V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE |
| #define BUF_TYPE_INPUT V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE |
| |
| static struct ion_client *venc_ion_client; |
| |
| struct index_bitmap { |
| unsigned long *bitmap; |
| int size; |
| int size_bits; /*Size in bits, not necessarily size/8 */ |
| }; |
| |
| struct venc_inst { |
| void *vidc_context; |
| struct mutex lock; |
| struct venc_msg_ops vmops; |
| struct mem_region registered_input_bufs, registered_output_bufs; |
| struct index_bitmap free_input_indices, free_output_indices; |
| int num_output_planes, num_input_planes; |
| struct task_struct *callback_thread; |
| bool callback_thread_running; |
| struct completion dq_complete, cmd_complete; |
| bool secure; |
| int domain; |
| }; |
| |
| int venc_load_fw(struct v4l2_subdev *sd) |
| { |
| /*No need to explicitly load the fw */ |
| return 0; |
| } |
| |
| int venc_init(struct v4l2_subdev *sd, u32 val) |
| { |
| if (!venc_ion_client) |
| venc_ion_client = msm_ion_client_create(-1, "wfd_enc_subdev"); |
| |
| return venc_ion_client ? 0 : -ENOMEM; |
| } |
| |
| static int next_free_index(struct index_bitmap *index_bitmap) |
| { |
| int index = find_first_zero_bit(index_bitmap->bitmap, |
| index_bitmap->size_bits); |
| |
| return (index >= index_bitmap->size_bits) ? |
| -1 : index; |
| } |
| |
| static int mark_index_busy(struct index_bitmap *index_bitmap, int index) |
| { |
| if (index > index_bitmap->size_bits) { |
| WFD_MSG_WARN("Marking unknown index as busy\n"); |
| return -EINVAL; |
| } |
| set_bit(index, index_bitmap->bitmap); |
| return 0; |
| } |
| |
| static int mark_index_free(struct index_bitmap *index_bitmap, int index) |
| { |
| if (index > index_bitmap->size_bits) { |
| WFD_MSG_WARN("Marking unknown index as free\n"); |
| return -EINVAL; |
| } |
| clear_bit(index, index_bitmap->bitmap); |
| return 0; |
| } |
| |
| static int get_list_len(struct mem_region *list) |
| { |
| struct mem_region *curr = NULL; |
| int index = 0; |
| list_for_each_entry(curr, &list->list, list) { |
| ++index; |
| } |
| |
| return index; |
| } |
| |
| static struct mem_region *get_registered_mregion(struct mem_region *list, |
| struct mem_region *mregion) |
| { |
| struct mem_region *curr = NULL; |
| list_for_each_entry(curr, &list->list, list) { |
| if (unlikely(mem_region_equals(curr, mregion))) |
| return curr; |
| } |
| |
| return NULL; |
| } |
| |
| static int venc_vidc_callback_thread(void *data) |
| { |
| struct venc_inst *inst = data; |
| WFD_MSG_DBG("Starting callback thread\n"); |
| while (!kthread_should_stop()) { |
| bool dequeue_buf = false; |
| struct v4l2_buffer buffer = {0}; |
| struct v4l2_event event = {0}; |
| int num_planes = 0; |
| int flags = msm_vidc_wait(inst->vidc_context); |
| |
| if (flags & POLLERR) { |
| WFD_MSG_ERR("Encoder reported error\n"); |
| break; |
| } |
| |
| if (flags & POLLPRI) { |
| bool bail_out = false; |
| |
| msm_vidc_dqevent(inst->vidc_context, &event); |
| if (event.type == V4L2_EVENT_MSM_VIDC_CLOSE_DONE) { |
| WFD_MSG_ERR("enc callback thread shutting " \ |
| "down normally\n"); |
| bail_out = true; |
| } else { |
| WFD_MSG_ERR("Got unknown event %d, ignoring\n", |
| event.id); |
| } |
| |
| complete_all(&inst->cmd_complete); |
| if (bail_out) |
| break; |
| } |
| |
| if (flags & POLLIN || flags & POLLRDNORM) { |
| buffer.type = BUF_TYPE_OUTPUT; |
| dequeue_buf = true; |
| num_planes = inst->num_output_planes; |
| WFD_MSG_DBG("Output buffer ready!\n"); |
| } |
| |
| if (flags & POLLOUT || flags & POLLWRNORM) { |
| buffer.type = BUF_TYPE_INPUT; |
| dequeue_buf = true; |
| num_planes = inst->num_input_planes; |
| WFD_MSG_DBG("Input buffer ready!\n"); |
| } |
| |
| if (dequeue_buf) { |
| int rc = 0; |
| struct v4l2_plane *planes = NULL; |
| struct mem_region *curr = NULL, *mregion = NULL; |
| struct list_head *reg_bufs = NULL; |
| struct index_bitmap *bitmap = NULL; |
| |
| planes = kzalloc(sizeof(*planes) * num_planes, |
| GFP_KERNEL); |
| buffer.m.planes = planes; |
| buffer.length = 1; |
| buffer.memory = V4L2_MEMORY_USERPTR; |
| rc = msm_vidc_dqbuf(inst->vidc_context, &buffer); |
| |
| if (rc) { |
| WFD_MSG_ERR("Error dequeuing buffer " \ |
| "from vidc: %d", rc); |
| goto abort_dequeue; |
| } |
| |
| reg_bufs = buffer.type == BUF_TYPE_OUTPUT ? |
| &inst->registered_output_bufs.list : |
| &inst->registered_input_bufs.list; |
| |
| bitmap = buffer.type == BUF_TYPE_OUTPUT ? |
| &inst->free_output_indices : |
| &inst->free_input_indices; |
| |
| list_for_each_entry(curr, reg_bufs, list) { |
| if ((u32)curr->paddr == |
| buffer.m.planes[0].m.userptr) { |
| mregion = curr; |
| break; |
| } |
| } |
| |
| if (!mregion) { |
| WFD_MSG_ERR("Got done msg for unknown buf\n"); |
| goto abort_dequeue; |
| } |
| |
| if (buffer.type == BUF_TYPE_OUTPUT && |
| inst->vmops.op_buffer_done) { |
| struct vb2_buffer *vb = |
| (struct vb2_buffer *)mregion->cookie; |
| |
| vb->v4l2_buf.flags = buffer.flags; |
| vb->v4l2_buf.timestamp = buffer.timestamp; |
| vb->v4l2_planes[0].bytesused = |
| buffer.m.planes[0].bytesused; |
| |
| inst->vmops.op_buffer_done( |
| inst->vmops.cbdata, 0, vb); |
| } else if (buffer.type == BUF_TYPE_INPUT && |
| inst->vmops.ip_buffer_done) { |
| inst->vmops.ip_buffer_done( |
| inst->vmops.cbdata, |
| 0, mregion); |
| } |
| |
| complete_all(&inst->dq_complete); |
| mutex_lock(&inst->lock); |
| mark_index_free(bitmap, buffer.index); |
| mutex_unlock(&inst->lock); |
| abort_dequeue: |
| kfree(planes); |
| } |
| } |
| |
| |
| WFD_MSG_DBG("Exiting callback thread\n"); |
| mutex_lock(&inst->lock); |
| inst->callback_thread_running = false; |
| mutex_unlock(&inst->lock); |
| return 0; |
| } |
| |
| static long venc_open(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct venc_msg_ops *vmops = arg; |
| struct v4l2_event_subscription event = {0}; |
| struct msm_vidc_iommu_info maps[MAX_MAP]; |
| int rc = 0; |
| |
| if (!vmops) { |
| WFD_MSG_ERR("Callbacks required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_open_fail; |
| } else if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_open_fail; |
| } |
| |
| inst = kzalloc(sizeof(*inst), GFP_KERNEL); |
| if (!inst) { |
| WFD_MSG_ERR("Failed to allocate memory\n"); |
| rc = -EINVAL; |
| goto venc_open_fail; |
| } |
| |
| inst->secure = false; |
| inst->vmops = *vmops; |
| INIT_LIST_HEAD(&inst->registered_output_bufs.list); |
| INIT_LIST_HEAD(&inst->registered_input_bufs.list); |
| init_completion(&inst->dq_complete); |
| init_completion(&inst->cmd_complete); |
| mutex_init(&inst->lock); |
| inst->vidc_context = msm_vidc_open(MSM_VIDC_CORE_0, MSM_VIDC_ENCODER); |
| if (!inst->vidc_context) { |
| WFD_MSG_ERR("Failed to create vidc context\n"); |
| rc = -ENXIO; |
| goto vidc_open_fail; |
| } |
| |
| event.type = V4L2_EVENT_MSM_VIDC_CLOSE_DONE; |
| rc = msm_vidc_subscribe_event(inst->vidc_context, &event); |
| if (rc) { |
| WFD_MSG_ERR("Failed to subscribe to CLOSE_DONE event\n"); |
| goto vidc_subscribe_fail; |
| } |
| |
| event.type = V4L2_EVENT_MSM_VIDC_FLUSH_DONE; |
| rc = msm_vidc_subscribe_event(inst->vidc_context, &event); |
| if (rc) { |
| WFD_MSG_ERR("Failed to subscribe to FLUSH_DONE event\n"); |
| goto vidc_subscribe_fail; |
| } |
| |
| rc = msm_vidc_get_iommu_maps(inst->vidc_context, maps); |
| if (rc) { |
| WFD_MSG_ERR("Failed to retreive domain mappings\n"); |
| rc = -ENODATA; |
| goto vidc_subscribe_fail; |
| } |
| |
| inst->domain = maps[inst->secure ? CP_MAP : NS_MAP].domain; |
| |
| inst->callback_thread = kthread_run(venc_vidc_callback_thread, inst, |
| "venc_vidc_callback_thread"); |
| if (IS_ERR(inst->callback_thread)) { |
| WFD_MSG_ERR("Failed to create callback thread\n"); |
| rc = PTR_ERR(inst->callback_thread); |
| inst->callback_thread = NULL; |
| goto vidc_kthread_create_fail; |
| } |
| inst->callback_thread_running = true; |
| |
| sd->dev_priv = inst; |
| vmops->cookie = inst; |
| return 0; |
| vidc_kthread_create_fail: |
| event.type = V4L2_EVENT_MSM_VIDC_CLOSE_DONE; |
| msm_vidc_unsubscribe_event(inst->vidc_context, &event); |
| |
| event.type = V4L2_EVENT_MSM_VIDC_FLUSH_DONE; |
| msm_vidc_unsubscribe_event(inst->vidc_context, &event); |
| vidc_subscribe_fail: |
| msm_vidc_close(inst->vidc_context); |
| vidc_open_fail: |
| kfree(inst); |
| venc_open_fail: |
| return rc; |
| } |
| |
| static long venc_close(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct v4l2_event_subscription event = {0}; |
| struct v4l2_encoder_cmd enc_cmd = {0}; |
| int rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_close_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| enc_cmd.cmd = V4L2_ENC_CMD_STOP; |
| msm_vidc_encoder_cmd(inst->vidc_context, &enc_cmd); |
| |
| wait_for_completion(&inst->cmd_complete); |
| |
| if (inst->callback_thread && inst->callback_thread_running) |
| kthread_stop(inst->callback_thread); |
| |
| event.type = V4L2_EVENT_MSM_VIDC_CLOSE_DONE; |
| rc = msm_vidc_unsubscribe_event(inst->vidc_context, &event); |
| if (rc) |
| WFD_MSG_WARN("Failed to unsubscribe close event\n"); |
| |
| event.type = V4L2_EVENT_MSM_VIDC_FLUSH_DONE; |
| rc = msm_vidc_unsubscribe_event(inst->vidc_context, &event); |
| if (rc) |
| WFD_MSG_WARN("Failed to unsubscribe flush event\n"); |
| |
| rc = msm_vidc_close(inst->vidc_context); |
| if (rc) |
| WFD_MSG_WARN("Failed to close vidc context\n"); |
| |
| kfree(inst); |
| sd->dev_priv = inst = NULL; |
| venc_close_fail: |
| return rc; |
| } |
| |
| static long venc_get_buffer_req(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| struct bufreq *bufreq = arg; |
| struct v4l2_requestbuffers v4l2_bufreq = {0}; |
| struct v4l2_format v4l2_format = {0}; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_buf_req_fail; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid buffer requirements\n"); |
| rc = -EINVAL; |
| goto venc_buf_req_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| /* Get buffer count */ |
| v4l2_bufreq = (struct v4l2_requestbuffers) { |
| .count = bufreq->count, |
| .type = BUF_TYPE_OUTPUT, |
| .memory = V4L2_MEMORY_USERPTR, |
| }; |
| |
| rc = msm_vidc_reqbufs(inst->vidc_context, &v4l2_bufreq); |
| if (rc) { |
| WFD_MSG_ERR("Failed getting buffer requirements\n"); |
| goto venc_buf_req_fail; |
| } |
| |
| /* Get buffer size */ |
| v4l2_format.type = BUF_TYPE_OUTPUT; |
| rc = msm_vidc_g_fmt(inst->vidc_context, &v4l2_format); |
| if (rc) { |
| WFD_MSG_ERR("Failed getting OP buffer size\n"); |
| goto venc_buf_req_fail; |
| } |
| |
| bufreq->count = v4l2_bufreq.count; |
| bufreq->size = v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage; |
| |
| inst->free_output_indices.size_bits = bufreq->count; |
| inst->free_output_indices.size = roundup(bufreq->count, |
| sizeof(unsigned long)) / sizeof(unsigned long); |
| inst->free_output_indices.bitmap = kzalloc(inst->free_output_indices. |
| size, GFP_KERNEL); |
| venc_buf_req_fail: |
| return rc; |
| } |
| |
| static long venc_set_buffer_req(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| struct bufreq *bufreq = arg; |
| struct v4l2_requestbuffers v4l2_bufreq = {0}; |
| struct v4l2_format v4l2_format = {0}; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_buf_req_fail; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid buffer requirements\n"); |
| rc = -EINVAL; |
| goto venc_buf_req_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| |
| /* Attempt to set buffer count */ |
| v4l2_bufreq = (struct v4l2_requestbuffers) { |
| .count = bufreq->count, |
| .type = BUF_TYPE_INPUT, |
| .memory = V4L2_MEMORY_USERPTR, |
| }; |
| |
| rc = msm_vidc_reqbufs(inst->vidc_context, &v4l2_bufreq); |
| if (rc) { |
| WFD_MSG_ERR("Failed getting buffer requirements"); |
| goto venc_buf_req_fail; |
| } |
| |
| /* Get buffer size */ |
| v4l2_format.type = BUF_TYPE_INPUT; |
| rc = msm_vidc_g_fmt(inst->vidc_context, &v4l2_format); |
| if (rc) { |
| WFD_MSG_ERR("Failed getting OP buffer size\n"); |
| goto venc_buf_req_fail; |
| } |
| |
| bufreq->count = v4l2_bufreq.count; |
| bufreq->size = v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage; |
| |
| inst->free_input_indices.size_bits = bufreq->count; |
| inst->free_input_indices.size = roundup(bufreq->count, |
| sizeof(unsigned long)) / sizeof(unsigned long); |
| inst->free_input_indices.bitmap = kzalloc(inst->free_input_indices. |
| size, GFP_KERNEL); |
| venc_buf_req_fail: |
| return rc; |
| } |
| |
| static long venc_start(struct v4l2_subdev *sd) |
| { |
| struct venc_inst *inst = NULL; |
| int rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_start_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| |
| rc = msm_vidc_streamon(inst->vidc_context, BUF_TYPE_OUTPUT); |
| if (rc) { |
| WFD_MSG_ERR("Failed to streamon vidc's output port"); |
| goto venc_start_fail; |
| } |
| |
| rc = msm_vidc_streamon(inst->vidc_context, BUF_TYPE_INPUT); |
| if (rc) { |
| WFD_MSG_ERR("Failed to streamon vidc's input port"); |
| goto venc_start_fail; |
| } |
| |
| venc_start_fail: |
| return rc; |
| } |
| |
| static long venc_stop(struct v4l2_subdev *sd) |
| { |
| struct venc_inst *inst = NULL; |
| int rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_stop_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| |
| rc = msm_vidc_streamoff(inst->vidc_context, BUF_TYPE_INPUT); |
| if (rc) { |
| WFD_MSG_ERR("Failed to streamoff vidc's input port"); |
| goto venc_stop_fail; |
| } |
| |
| rc = msm_vidc_streamoff(inst->vidc_context, BUF_TYPE_OUTPUT); |
| if (rc) { |
| WFD_MSG_ERR("Failed to streamoff vidc's output port"); |
| goto venc_stop_fail; |
| } |
| |
| venc_stop_fail: |
| return rc; |
| } |
| |
| static long venc_set_input_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| struct v4l2_buffer buf = {0}; |
| struct v4l2_plane plane = {0}; |
| struct mem_region *mregion = arg; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto set_input_buffer_fail; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid input buffer\n"); |
| rc = -EINVAL; |
| goto set_input_buffer_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| if (get_registered_mregion(&inst->registered_input_bufs, mregion)) { |
| WFD_MSG_ERR("Duplicate input buffer\n"); |
| rc = -EEXIST; |
| goto set_input_buffer_fail; |
| } |
| |
| mregion = kzalloc(sizeof(*mregion), GFP_KERNEL); |
| *mregion = *(struct mem_region *)arg; |
| |
| plane = (struct v4l2_plane) { |
| .length = mregion->size, |
| .m.userptr = (u32)mregion->paddr, |
| }; |
| |
| buf = (struct v4l2_buffer) { |
| .index = get_list_len(&inst->registered_input_bufs), |
| .type = BUF_TYPE_INPUT, |
| .bytesused = 0, |
| .memory = V4L2_MEMORY_USERPTR, |
| .m.planes = &plane, |
| .length = 1, |
| }; |
| |
| WFD_MSG_DBG("Prepare %p with index, %d", |
| (void *)buf.m.planes[0].m.userptr, buf.index); |
| rc = msm_vidc_prepare_buf(inst->vidc_context, &buf); |
| if (rc) { |
| WFD_MSG_ERR("Failed to prepare input buffer\n"); |
| goto set_input_buffer_fail; |
| } |
| |
| list_add_tail(&mregion->list, &inst->registered_input_bufs.list); |
| return 0; |
| set_input_buffer_fail: |
| kfree(mregion); |
| return rc; |
| } |
| |
| static int venc_map_user_to_kernel(struct venc_inst *inst, |
| struct mem_region *mregion) |
| { |
| int rc = 0; |
| unsigned long flags = 0, size = 0; |
| if (!mregion) { |
| rc = -EINVAL; |
| goto venc_map_fail; |
| } |
| |
| mregion->ion_handle = ion_import_dma_buf(venc_ion_client, mregion->fd); |
| if (IS_ERR_OR_NULL(mregion->ion_handle)) { |
| rc = PTR_ERR(mregion->ion_handle); |
| WFD_MSG_ERR("Failed to get handle: %p, %d, %d, %d\n", |
| venc_ion_client, mregion->fd, mregion->offset, rc); |
| mregion->ion_handle = NULL; |
| goto venc_map_fail; |
| } |
| |
| rc = ion_handle_get_flags(venc_ion_client, mregion->ion_handle, &flags); |
| if (rc) { |
| WFD_MSG_ERR("Failed to get ion flags %d\n", rc); |
| goto venc_map_fail; |
| } |
| |
| mregion->kvaddr = ion_map_kernel(venc_ion_client, |
| mregion->ion_handle); |
| |
| if (IS_ERR_OR_NULL(mregion->kvaddr)) { |
| WFD_MSG_ERR("Failed to map buffer into kernel\n"); |
| rc = PTR_ERR(mregion->kvaddr); |
| mregion->kvaddr = NULL; |
| goto venc_map_fail; |
| } |
| |
| rc = ion_map_iommu(venc_ion_client, mregion->ion_handle, |
| inst->domain, 0, SZ_4K, 0, |
| (unsigned long *)&mregion->paddr, &size, flags, 0); |
| |
| if (rc) { |
| WFD_MSG_ERR("Failed to map into iommu\n"); |
| goto venc_map_iommu_map_fail; |
| } else if (size < mregion->size) { |
| WFD_MSG_ERR("Failed to iommu map the correct size\n"); |
| goto venc_map_iommu_size_fail; |
| } |
| |
| return 0; |
| venc_map_iommu_size_fail: |
| ion_unmap_iommu(venc_ion_client, mregion->ion_handle, |
| inst->domain, 0); |
| venc_map_iommu_map_fail: |
| ion_unmap_kernel(venc_ion_client, mregion->ion_handle); |
| venc_map_fail: |
| return rc; |
| } |
| |
| static int venc_unmap_user_to_kernel(struct venc_inst *inst, |
| struct mem_region *mregion) |
| { |
| if (!mregion || !mregion->ion_handle) |
| return 0; |
| |
| if (mregion->paddr) { |
| ion_unmap_iommu(venc_ion_client, mregion->ion_handle, |
| inst->domain, 0); |
| mregion->paddr = NULL; |
| } |
| |
| if (mregion->kvaddr) { |
| ion_unmap_kernel(venc_ion_client, mregion->ion_handle); |
| mregion->kvaddr = NULL; |
| } |
| |
| |
| return 0; |
| } |
| |
| static long venc_set_output_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| struct v4l2_buffer buf = {0}; |
| struct v4l2_plane plane = {0}; |
| struct mem_region *mregion = arg; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_set_output_buffer_fail; |
| } else if (!mregion) { |
| WFD_MSG_ERR("Invalid output buffer\n"); |
| rc = -EINVAL; |
| goto venc_set_output_buffer_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| |
| /* Check if buf already registered */ |
| if (get_registered_mregion(&inst->registered_output_bufs, mregion)) { |
| WFD_MSG_ERR("Duplicate output buffer\n"); |
| rc = -EEXIST; |
| goto venc_set_output_buffer_fail; |
| } |
| |
| mregion = kzalloc(sizeof(*mregion), GFP_KERNEL); |
| |
| if (!mregion) { |
| WFD_MSG_ERR("Failed to allocate memory\n"); |
| goto venc_set_output_buffer_fail; |
| } |
| |
| *mregion = *(struct mem_region *)arg; |
| INIT_LIST_HEAD(&mregion->list); |
| |
| rc = venc_map_user_to_kernel(inst, mregion); |
| if (rc) { |
| WFD_MSG_ERR("Failed to map output buffer\n"); |
| goto venc_set_output_buffer_map_fail; |
| } |
| |
| plane = (struct v4l2_plane) { |
| .length = mregion->size, |
| .m.userptr = (u32)mregion->paddr, |
| }; |
| |
| buf = (struct v4l2_buffer) { |
| .index = get_list_len(&inst->registered_output_bufs), |
| .type = BUF_TYPE_OUTPUT, |
| .bytesused = 0, |
| .memory = V4L2_MEMORY_USERPTR, |
| .m.planes = &plane, |
| .length = 1, |
| }; |
| |
| WFD_MSG_DBG("Prepare %p with index, %d", |
| (void *)buf.m.planes[0].m.userptr, buf.index); |
| rc = msm_vidc_prepare_buf(inst->vidc_context, &buf); |
| if (rc) { |
| WFD_MSG_ERR("Failed to prepare output buffer\n"); |
| goto venc_set_output_buffer_prepare_fail; |
| } |
| |
| list_add_tail(&mregion->list, &inst->registered_output_bufs.list); |
| return rc; |
| venc_set_output_buffer_prepare_fail: |
| venc_unmap_user_to_kernel(inst, mregion); |
| venc_set_output_buffer_map_fail: |
| kfree(mregion); |
| venc_set_output_buffer_fail: |
| return rc; |
| } |
| |
| static long venc_set_format(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct v4l2_format *fmt = arg, temp; |
| int rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_set_format_fail; |
| } else if (!fmt) { |
| WFD_MSG_ERR("Invalid format\n"); |
| rc = -EINVAL; |
| goto venc_set_format_fail; |
| } else if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { |
| WFD_MSG_ERR("Invalid buffer type %d\n", fmt->type); |
| rc = -ENOTSUPP; |
| goto venc_set_format_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| temp = (struct v4l2_format) { |
| .type = BUF_TYPE_OUTPUT, |
| .fmt.pix_mp = (struct v4l2_pix_format_mplane) { |
| .width = fmt->fmt.pix.width, |
| .height = fmt->fmt.pix.height, |
| .pixelformat = fmt->fmt.pix.pixelformat, |
| }, |
| }; |
| |
| rc = msm_vidc_s_fmt(inst->vidc_context, &temp); |
| |
| if (rc) { |
| WFD_MSG_ERR("Failed to format for output port\n"); |
| goto venc_set_format_fail; |
| } else if (!temp.fmt.pix_mp.num_planes) { |
| WFD_MSG_ERR("No. of planes for output buffers make no sense\n"); |
| rc = -EINVAL; |
| goto venc_set_format_fail; |
| } |
| fmt->fmt.pix.sizeimage = temp.fmt.pix_mp.plane_fmt[0].sizeimage; |
| inst->num_output_planes = temp.fmt.pix_mp.num_planes; |
| |
| temp.type = BUF_TYPE_INPUT; |
| temp.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; |
| rc = msm_vidc_s_fmt(inst->vidc_context, &temp); |
| inst->num_input_planes = temp.fmt.pix_mp.num_planes; |
| |
| if (rc) { |
| WFD_MSG_ERR("Failed to format for input port\n"); |
| goto venc_set_format_fail; |
| } |
| venc_set_format_fail: |
| return rc; |
| } |
| |
| static long venc_set_framerate(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct v4l2_control ctrl = {0}; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid framerate\n"); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| ctrl.id = V4L2_CID_MPEG_VIDC_VIDEO_FRAME_RATE; |
| ctrl.value = 30; |
| return msm_vidc_s_ctrl(inst->vidc_context, &ctrl); |
| } |
| |
| static long venc_fill_outbuf(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct mem_region *mregion = NULL; |
| struct v4l2_buffer buffer = {0}; |
| struct v4l2_plane plane = {0}; |
| int index = 0, rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid output buffer ot fill\n"); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| mregion = get_registered_mregion(&inst->registered_output_bufs, arg); |
| |
| if (!mregion) { |
| WFD_MSG_ERR("Output buffer not registered\n"); |
| return -ENOENT; |
| } |
| |
| plane = (struct v4l2_plane) { |
| .length = mregion->size, |
| .m.userptr = (u32)mregion->paddr, |
| }; |
| |
| while (true) { |
| mutex_lock(&inst->lock); |
| index = next_free_index(&inst->free_output_indices); |
| mutex_unlock(&inst->lock); |
| |
| if (index < 0) |
| wait_for_completion(&inst->dq_complete); |
| else |
| break; |
| } |
| |
| buffer = (struct v4l2_buffer) { |
| .index = index, |
| .type = BUF_TYPE_OUTPUT, |
| .memory = V4L2_MEMORY_USERPTR, |
| .m.planes = &plane, |
| .length = 1, |
| }; |
| |
| WFD_MSG_DBG("Fill buffer %p with index, %d", |
| (void *)buffer.m.planes[0].m.userptr, buffer.index); |
| rc = msm_vidc_qbuf(inst->vidc_context, &buffer); |
| if (!rc) { |
| mutex_lock(&inst->lock); |
| mark_index_busy(&inst->free_output_indices, index); |
| mutex_unlock(&inst->lock); |
| } |
| return rc; |
| |
| } |
| |
| static long venc_encode_frame(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct venc_buf_info *venc_buf = arg; |
| struct mem_region *mregion = NULL; |
| struct v4l2_buffer buffer = {0}; |
| struct v4l2_plane plane = {0}; |
| int index = 0, rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } else if (!venc_buf) { |
| WFD_MSG_ERR("Invalid output buffer ot fill\n"); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| mregion = venc_buf->mregion; |
| |
| plane = (struct v4l2_plane) { |
| .length = mregion->size, |
| .m.userptr = (u32)mregion->paddr, |
| .bytesused = mregion->size, |
| }; |
| |
| while (true) { |
| mutex_lock(&inst->lock); |
| index = next_free_index(&inst->free_input_indices); |
| mutex_unlock(&inst->lock); |
| |
| if (index < 0) |
| wait_for_completion(&inst->dq_complete); |
| else |
| break; |
| } |
| |
| buffer = (struct v4l2_buffer) { |
| .index = index, |
| .type = BUF_TYPE_INPUT, |
| .timestamp = ns_to_timeval(venc_buf->timestamp), |
| .memory = V4L2_MEMORY_USERPTR, |
| .m.planes = &plane, |
| .length = 1, |
| }; |
| |
| WFD_MSG_DBG("Encode buffer %p with index, %d", |
| (void *)buffer.m.planes[0].m.userptr, buffer.index); |
| rc = msm_vidc_qbuf(inst->vidc_context, &buffer); |
| if (!rc) { |
| mutex_lock(&inst->lock); |
| mark_index_busy(&inst->free_input_indices, index); |
| mutex_unlock(&inst->lock); |
| } |
| return rc; |
| } |
| |
| static long venc_alloc_recon_buffers(struct v4l2_subdev *sd, void *arg) |
| { |
| /* vidc driver allocates internally on streamon */ |
| return 0; |
| } |
| |
| static long venc_free_buffer(struct venc_inst *inst, int type, |
| struct mem_region *to_free, bool unmap_user_buffer) |
| { |
| struct mem_region *mregion = NULL; |
| struct mem_region *buf_list = NULL; |
| |
| if (type == BUF_TYPE_OUTPUT) { |
| buf_list = &inst->registered_output_bufs; |
| } else if (type == BUF_TYPE_INPUT) { |
| buf_list = &inst->registered_input_bufs; |
| } else { |
| WFD_MSG_ERR("Trying to free a buffer of unknown type\n"); |
| return -EINVAL; |
| } |
| |
| mregion = get_registered_mregion(buf_list, to_free); |
| |
| if (!mregion) { |
| WFD_MSG_ERR("Buffer not registered, cannot free\n"); |
| return -ENOENT; |
| } |
| |
| if (unmap_user_buffer) { |
| int rc = venc_unmap_user_to_kernel(inst, mregion); |
| if (rc) |
| WFD_MSG_WARN("Unable to unmap user buffer\n"); |
| } |
| |
| list_del(&mregion->list); |
| kfree(mregion); |
| return 0; |
| } |
| static long venc_free_output_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_free_output_buffer_fail; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid output buffer\n"); |
| rc = -EINVAL; |
| goto venc_free_output_buffer_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| return venc_free_buffer(inst, BUF_TYPE_OUTPUT, arg, true); |
| venc_free_output_buffer_fail: |
| return rc; |
| } |
| |
| static long venc_flush_buffers(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct v4l2_encoder_cmd enc_cmd = {0}; |
| int rc = 0; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_flush_buffers_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| |
| enc_cmd.cmd = V4L2_ENC_QCOM_CMD_FLUSH; |
| enc_cmd.flags = V4L2_QCOM_CMD_FLUSH_OUTPUT | |
| V4L2_QCOM_CMD_FLUSH_CAPTURE; |
| msm_vidc_encoder_cmd(inst->vidc_context, &enc_cmd); |
| |
| wait_for_completion(&inst->cmd_complete); |
| venc_flush_buffers_fail: |
| return rc; |
| } |
| |
| static long venc_free_input_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| int rc = 0; |
| struct venc_inst *inst = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| rc = -EINVAL; |
| goto venc_free_input_buffer_fail; |
| } else if (!arg) { |
| WFD_MSG_ERR("Invalid output buffer\n"); |
| rc = -EINVAL; |
| goto venc_free_input_buffer_fail; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| return venc_free_buffer(inst, BUF_TYPE_INPUT, arg, false); |
| venc_free_input_buffer_fail: |
| return rc; |
| } |
| |
| static long venc_free_recon_buffers(struct v4l2_subdev *sd, void *arg) |
| { |
| /* vidc driver takes care of this */ |
| return 0; |
| } |
| |
| static long venc_set_property(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| struct v4l2_control *ctrl = arg; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| if (ctrl->id == V4L2_CID_MPEG_VIDEO_HEADER_MODE) { |
| /* XXX: We don't support this yet, but to prevent unncessary |
| * target specific code for the client, we'll not error out. |
| * The client ideally shouldn't notice this */ |
| return 0; |
| } |
| |
| return msm_vidc_s_ctrl(inst->vidc_context, (struct v4l2_control *)arg); |
| } |
| |
| static long venc_get_property(struct v4l2_subdev *sd, void *arg) |
| { |
| struct venc_inst *inst = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| return msm_vidc_g_ctrl(inst->vidc_context, (struct v4l2_control *)arg); |
| } |
| |
| long venc_mmap(struct v4l2_subdev *sd, void *arg) |
| { |
| struct mem_region_map *mmap = arg; |
| struct mem_region *mregion = NULL; |
| unsigned long rc = 0, size = 0; |
| void *paddr = NULL; |
| struct venc_inst *inst = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } else if (!mmap || !mmap->mregion) { |
| WFD_MSG_ERR("Memregion required for %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| mregion = mmap->mregion; |
| if (mregion->size % SZ_4K != 0) { |
| WFD_MSG_ERR("Memregion not aligned to %d\n", SZ_4K); |
| return -EINVAL; |
| } |
| |
| rc = ion_map_iommu(mmap->ion_client, mregion->ion_handle, |
| inst->domain, 0, SZ_4K, 0, (unsigned long *)&paddr, |
| &size, 0, 0); |
| |
| if (rc) { |
| WFD_MSG_ERR("Failed to get physical addr\n"); |
| paddr = NULL; |
| } else if (size < mregion->size) { |
| WFD_MSG_ERR("Failed to map enough memory\n"); |
| rc = -ENOMEM; |
| } |
| |
| mregion->paddr = paddr; |
| return rc; |
| } |
| |
| long venc_munmap(struct v4l2_subdev *sd, void *arg) |
| { |
| struct mem_region_map *mmap = arg; |
| struct mem_region *mregion = NULL; |
| struct venc_inst *inst = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("Subdevice required for %s\n", __func__); |
| return -EINVAL; |
| } else if (!mmap || !mmap->mregion) { |
| WFD_MSG_ERR("Memregion required for %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| inst = (struct venc_inst *)sd->dev_priv; |
| mregion = mmap->mregion; |
| |
| ion_unmap_iommu(mmap->ion_client, mregion->ion_handle, |
| inst->domain, 0); |
| return 0; |
| } |
| |
| static long venc_set_framerate_mode(struct v4l2_subdev *sd, |
| void *arg) |
| { |
| /* TODO: Unsupported for now, but return false success |
| * to preserve binary compatibility for userspace apps |
| * across targets */ |
| return 0; |
| } |
| |
| long venc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) |
| { |
| long rc = 0; |
| switch (cmd) { |
| case OPEN: |
| rc = venc_open(sd, arg); |
| break; |
| case CLOSE: |
| rc = venc_close(sd, arg); |
| break; |
| case ENCODE_START: |
| rc = venc_start(sd); |
| break; |
| case ENCODE_FRAME: |
| venc_encode_frame(sd, arg); |
| break; |
| case ENCODE_STOP: |
| rc = venc_stop(sd); |
| break; |
| case SET_PROP: |
| rc = venc_set_property(sd, arg); |
| break; |
| case GET_PROP: |
| rc = venc_get_property(sd, arg); |
| break; |
| case GET_BUFFER_REQ: |
| rc = venc_get_buffer_req(sd, arg); |
| break; |
| case SET_BUFFER_REQ: |
| rc = venc_set_buffer_req(sd, arg); |
| break; |
| case FREE_BUFFER: |
| break; |
| case FILL_OUTPUT_BUFFER: |
| rc = venc_fill_outbuf(sd, arg); |
| break; |
| case SET_FORMAT: |
| rc = venc_set_format(sd, arg); |
| break; |
| case SET_FRAMERATE: |
| rc = venc_set_framerate(sd, arg); |
| break; |
| case SET_INPUT_BUFFER: |
| rc = venc_set_input_buffer(sd, arg); |
| break; |
| case SET_OUTPUT_BUFFER: |
| rc = venc_set_output_buffer(sd, arg); |
| break; |
| case ALLOC_RECON_BUFFERS: |
| rc = venc_alloc_recon_buffers(sd, arg); |
| break; |
| case FREE_OUTPUT_BUFFER: |
| rc = venc_free_output_buffer(sd, arg); |
| break; |
| case FREE_INPUT_BUFFER: |
| rc = venc_free_input_buffer(sd, arg); |
| break; |
| case FREE_RECON_BUFFERS: |
| rc = venc_free_recon_buffers(sd, arg); |
| break; |
| case ENCODE_FLUSH: |
| rc = venc_flush_buffers(sd, arg); |
| break; |
| case ENC_MMAP: |
| rc = venc_mmap(sd, arg); |
| break; |
| case ENC_MUNMAP: |
| rc = venc_munmap(sd, arg); |
| break; |
| case SET_FRAMERATE_MODE: |
| rc = venc_set_framerate_mode(sd, arg); |
| break; |
| default: |
| WFD_MSG_ERR("Unknown ioctl %d to enc-subdev\n", cmd); |
| rc = -ENOTSUPP; |
| break; |
| } |
| return rc; |
| } |