| /* Copyright (c) 2014-2018, 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/vmalloc.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/ion.h> |
| #include <linux/msm_ion.h> |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-event.h> |
| #include <media/videobuf2-v4l2.h> |
| #include <linux/clk/msm-clk.h> |
| |
| #include "msm_fd_dev.h" |
| #include "msm_fd_hw.h" |
| #include "msm_fd_regs.h" |
| |
| #define MSM_FD_DRV_NAME "msm_fd" |
| |
| #define MSM_FD_WORD_SIZE_BYTES 4 |
| |
| /* Face detection thresholds definitions */ |
| #define MSM_FD_DEF_THRESHOLD 5 |
| #define MSM_FD_MAX_THRESHOLD_VALUE 9 |
| |
| /* Face angle lookup table */ |
| #define MSM_FD_DEF_ANGLE_IDX 2 |
| static int msm_fd_angle[] = {45, 135, 359}; |
| |
| /* Face direction lookup table */ |
| #define MSM_FD_DEF_DIR_IDX 0 |
| static int msm_fd_dir[] = {0, 90, 270, 180}; |
| |
| /* Minimum face size lookup table */ |
| #define MSM_FD_DEF_MIN_SIZE_IDX 0 |
| static int msm_fd_min_size[] = {20, 25, 32, 40}; |
| |
| /* Face detection size lookup table */ |
| static struct msm_fd_size fd_size[] = { |
| { |
| .width = 320, |
| .height = 240, |
| .reg_val = MSM_FD_IMAGE_SIZE_QVGA, |
| .work_size = (13120 * MSM_FD_WORD_SIZE_BYTES), |
| }, |
| { |
| .width = 427, |
| .height = 240, |
| .reg_val = MSM_FD_IMAGE_SIZE_WQVGA, |
| .work_size = (17744 * MSM_FD_WORD_SIZE_BYTES), |
| }, |
| { |
| .width = 640, |
| .height = 480, |
| .reg_val = MSM_FD_IMAGE_SIZE_VGA, |
| .work_size = (52624 * MSM_FD_WORD_SIZE_BYTES), |
| }, |
| { |
| .width = 854, |
| .height = 480, |
| .reg_val = MSM_FD_IMAGE_SIZE_WVGA, |
| .work_size = (70560 * MSM_FD_WORD_SIZE_BYTES), |
| }, |
| }; |
| |
| /* |
| * msm_fd_ctx_from_fh - Get fd context from v4l2 fh. |
| * @fh: Pointer to v4l2 fh. |
| */ |
| static inline struct fd_ctx *msm_fd_ctx_from_fh(struct v4l2_fh *fh) |
| { |
| return container_of(fh, struct fd_ctx, fh); |
| } |
| |
| /* |
| * msm_fd_get_format_index - Get format index from v4l2 format. |
| * @f: Pointer to v4l2 format struct. |
| */ |
| static int msm_fd_get_format_index(struct v4l2_format *f) |
| { |
| int index; |
| |
| for (index = 0; index < ARRAY_SIZE(fd_size); index++) { |
| if (f->fmt.pix.width <= fd_size[index].width && |
| f->fmt.pix.height <= fd_size[index].height) |
| return index; |
| } |
| return index - 1; |
| } |
| |
| /* |
| * msm_fd_get_idx_from_value - Get array index from value. |
| * @value: Value for which index is needed. |
| * @array: Array in which index is searched for. |
| * @array_size: Array size. |
| */ |
| static int msm_fd_get_idx_from_value(int value, int *array, int array_size) |
| { |
| int index; |
| int i; |
| |
| index = 0; |
| for (i = 1; i < array_size; i++) { |
| if (value == array[i]) { |
| index = i; |
| break; |
| } |
| if (abs(value - array[i]) < abs(value - array[index])) |
| index = i; |
| } |
| return index; |
| } |
| |
| /* |
| * msm_fd_fill_format_from_index - Fill v4l2 format struct from size index. |
| * @f: Pointer of v4l2 struct which will be filled. |
| * @index: Size index (Format will be filled based on this index). |
| */ |
| static int msm_fd_fill_format_from_index(struct v4l2_format *f, int index) |
| { |
| f->fmt.pix.width = fd_size[index].width; |
| f->fmt.pix.height = fd_size[index].height; |
| f->fmt.pix.pixelformat = V4L2_PIX_FMT_GREY; |
| if (f->fmt.pix.bytesperline < f->fmt.pix.width) |
| f->fmt.pix.bytesperline = f->fmt.pix.width; |
| |
| f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline, 16); |
| f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; |
| f->fmt.pix.field = V4L2_FIELD_NONE; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_fill_format_from_ctx - Fill v4l2 format struct from fd context. |
| * @f: Pointer of v4l2 struct which will be filled. |
| * @c: Pointer to fd context. |
| */ |
| static int msm_fd_fill_format_from_ctx(struct v4l2_format *f, struct fd_ctx *c) |
| { |
| if (c->format.size == NULL) |
| return -EINVAL; |
| |
| f->fmt.pix.width = c->format.size->width; |
| f->fmt.pix.height = c->format.size->height; |
| f->fmt.pix.pixelformat = c->format.pixelformat; |
| f->fmt.pix.bytesperline = c->format.bytesperline; |
| f->fmt.pix.sizeimage = c->format.sizeimage; |
| f->fmt.pix.field = V4L2_FIELD_NONE; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_queue_setup - vb2_ops queue_setup callback. |
| * @q: Pointer to vb2 queue struct. |
| * @fmt: Pointer to v4l2 format struct (NULL is valid argument). |
| * @num_buffers: Pointer of number of buffers requested. |
| * @num_planes: Pointer to number of planes requested. |
| * @sizes: Array containing sizes of planes. |
| * @alloc_ctxs: Array of allocated contexts for each plane. |
| */ |
| static int msm_fd_queue_setup(struct vb2_queue *q, |
| // const void *parg, |
| unsigned int *num_buffers, unsigned int *num_planes, |
| unsigned int sizes[], struct device *alloc_ctxs[]) |
| { |
| struct fd_ctx *ctx = vb2_get_drv_priv(q); |
| //const struct v4l2_format *fmt = parg; |
| const struct v4l2_format *fmt = NULL; |
| |
| *num_planes = 1; |
| |
| if (fmt == NULL) |
| sizes[0] = ctx->format.sizeimage; |
| else |
| sizes[0] = fmt->fmt.pix.sizeimage; |
| |
| alloc_ctxs[0] = (struct device *)&ctx->mem_pool; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_buf_init - vb2_ops buf_init callback. |
| * @vb: Pointer to vb2 buffer struct. |
| */ |
| static int msm_fd_buf_init(struct vb2_buffer *vb) |
| { |
| struct msm_fd_buffer *fd_buffer = |
| (struct msm_fd_buffer *)vb; |
| |
| INIT_LIST_HEAD(&fd_buffer->list); |
| atomic_set(&fd_buffer->active, 0); |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_buf_queue - vb2_ops buf_queue callback. |
| * @vb: Pointer to vb2 buffer struct. |
| */ |
| static void msm_fd_buf_queue(struct vb2_buffer *vb) |
| { |
| struct fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); |
| struct msm_fd_buffer *fd_buffer = |
| (struct msm_fd_buffer *)vb; |
| |
| fd_buffer->format = ctx->format; |
| fd_buffer->settings = ctx->settings; |
| fd_buffer->work_addr = ctx->work_buf.addr; |
| msm_fd_hw_add_buffer(ctx->fd_device, fd_buffer); |
| |
| if (vb->vb2_queue->streaming) |
| msm_fd_hw_schedule_and_start(ctx->fd_device); |
| |
| } |
| |
| /* |
| * msm_fd_start_streaming - vb2_ops start_streaming callback. |
| * @q: Pointer to vb2 queue struct. |
| * @count: Number of buffer queued before stream on call. |
| */ |
| static int msm_fd_start_streaming(struct vb2_queue *q, unsigned int count) |
| { |
| struct fd_ctx *ctx = vb2_get_drv_priv(q); |
| int ret; |
| |
| if (ctx->work_buf.fd == -1) { |
| dev_err(ctx->fd_device->dev, "Missing working buffer\n"); |
| return -EINVAL; |
| } |
| |
| ret = msm_fd_hw_get(ctx->fd_device, ctx->settings.speed); |
| if (ret < 0) { |
| dev_err(ctx->fd_device->dev, "Can not acquire fd hw\n"); |
| goto out; |
| } |
| |
| ret = msm_fd_hw_schedule_and_start(ctx->fd_device); |
| if (ret < 0) |
| dev_err(ctx->fd_device->dev, "Can not start fd hw\n"); |
| |
| out: |
| return ret; |
| } |
| |
| /* |
| * msm_fd_stop_streaming - vb2_ops stop_streaming callback. |
| * @q: Pointer to vb2 queue struct. |
| */ |
| static void msm_fd_stop_streaming(struct vb2_queue *q) |
| { |
| struct fd_ctx *ctx = vb2_get_drv_priv(q); |
| |
| mutex_lock(&ctx->fd_device->recovery_lock); |
| msm_fd_hw_remove_buffers_from_queue(ctx->fd_device, q); |
| msm_fd_hw_put(ctx->fd_device); |
| mutex_unlock(&ctx->fd_device->recovery_lock); |
| } |
| |
| /* Videobuf2 queue callbacks. */ |
| static struct vb2_ops msm_fd_vb2_q_ops = { |
| .queue_setup = msm_fd_queue_setup, |
| .buf_init = msm_fd_buf_init, |
| .buf_queue = msm_fd_buf_queue, |
| .start_streaming = msm_fd_start_streaming, |
| .stop_streaming = msm_fd_stop_streaming, |
| }; |
| |
| /* |
| * msm_fd_get_userptr - Map and get buffer handler for user pointer buffer. |
| * @alloc_ctx: Contexts allocated in buf_setup. |
| * @vaddr: Virtual addr passed from userpsace (in our case ion fd) |
| * @size: Size of the buffer |
| * @write: True if buffer will be used for writing the data. |
| */ |
| static void *msm_fd_get_userptr(struct device *alloc_ctx, |
| unsigned long vaddr, unsigned long size, |
| enum dma_data_direction dma_dir) |
| { |
| struct msm_fd_mem_pool *pool = (void *)alloc_ctx; |
| struct msm_fd_buf_handle *buf; |
| int ret; |
| |
| buf = kzalloc(sizeof(*buf), GFP_KERNEL); |
| if (!buf) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = msm_fd_hw_map_buffer(pool, vaddr, buf); |
| if (ret < 0 || buf->size < size) |
| goto error; |
| |
| return buf; |
| error: |
| kzfree(buf); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| /* |
| * msm_fd_put_userptr - Unmap and free buffer handler. |
| * @buf_priv: Buffer handler allocated get_userptr callback. |
| */ |
| static void msm_fd_put_userptr(void *buf_priv) |
| { |
| if (IS_ERR_OR_NULL(buf_priv)) |
| return; |
| |
| msm_fd_hw_unmap_buffer(buf_priv); |
| |
| kzfree(buf_priv); |
| } |
| |
| /* Videobuf2 memory callbacks. */ |
| static struct vb2_mem_ops msm_fd_vb2_mem_ops = { |
| .get_userptr = msm_fd_get_userptr, |
| .put_userptr = msm_fd_put_userptr, |
| }; |
| |
| /* |
| * msm_fd_vbif_error_handler - FD VBIF Error handler |
| * @handle: FD Device handle |
| * @error: CPP-VBIF Error code |
| */ |
| static int msm_fd_vbif_error_handler(void *handle, uint32_t error) |
| { |
| struct fd_ctx *ctx; |
| struct msm_fd_device *fd; |
| struct msm_fd_buffer *active_buf; |
| int ret; |
| |
| if (handle == NULL) |
| return 0; |
| |
| ctx = (struct fd_ctx *)handle; |
| fd = (struct msm_fd_device *)ctx->fd_device; |
| |
| if (error == CPP_VBIF_ERROR_HANG) { |
| mutex_lock(&fd->recovery_lock); |
| dev_err(fd->dev, "Handling FD VBIF Hang\n"); |
| if (fd->state != MSM_FD_DEVICE_RUNNING) { |
| dev_err(fd->dev, "FD is not FD_DEVICE_RUNNING, %d\n", |
| fd->state); |
| mutex_unlock(&fd->recovery_lock); |
| return 0; |
| } |
| fd->recovery_mode = 1; |
| |
| /* Halt and reset */ |
| msm_fd_hw_put(fd); |
| msm_fd_hw_get(fd, ctx->settings.speed); |
| |
| /* Get active buffer */ |
| MSM_FD_SPIN_LOCK(fd->slock, 1); |
| active_buf = msm_fd_hw_get_active_buffer(fd, 1); |
| MSM_FD_SPIN_UNLOCK(fd->slock, 1); |
| |
| if (active_buf == NULL) { |
| dev_dbg(fd->dev, "no active buffer, return\n"); |
| fd->recovery_mode = 0; |
| mutex_unlock(&fd->recovery_lock); |
| return 0; |
| } |
| |
| dev_dbg(fd->dev, "Active Buffer present.. Start re-schedule\n"); |
| |
| /* Queue the buffer again */ |
| msm_fd_hw_add_buffer(fd, active_buf); |
| |
| /* Schedule and restart */ |
| ret = msm_fd_hw_schedule_next_buffer(fd, 1); |
| if (ret) { |
| dev_err(fd->dev, "Cannot reschedule buffer, recovery failed\n"); |
| fd->recovery_mode = 0; |
| mutex_unlock(&fd->recovery_lock); |
| return ret; |
| } |
| dev_dbg(fd->dev, "Restarted FD after VBIF HAng\n"); |
| mutex_unlock(&fd->recovery_lock); |
| } |
| return 0; |
| } |
| |
| /* |
| * msm_fd_open - Fd device open method. |
| * @file: Pointer to file struct. |
| */ |
| static int msm_fd_open(struct file *file) |
| { |
| struct msm_fd_device *device = video_drvdata(file); |
| struct video_device *video = video_devdata(file); |
| struct fd_ctx *ctx; |
| int ret; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| ctx->fd_device = device; |
| |
| /* Initialize work buffer handler */ |
| ctx->work_buf.pool = NULL; |
| ctx->work_buf.fd = -1; |
| |
| /* Set ctx defaults */ |
| ctx->settings.speed = ctx->fd_device->clk_rates_num - 1; |
| ctx->settings.angle_index = MSM_FD_DEF_ANGLE_IDX; |
| ctx->settings.direction_index = MSM_FD_DEF_DIR_IDX; |
| ctx->settings.min_size_index = MSM_FD_DEF_MIN_SIZE_IDX; |
| ctx->settings.threshold = MSM_FD_DEF_THRESHOLD; |
| |
| atomic_set(&ctx->subscribed_for_event, 0); |
| |
| v4l2_fh_init(&ctx->fh, video); |
| |
| file->private_data = &ctx->fh; |
| v4l2_fh_add(&ctx->fh); |
| |
| ctx->vb2_q.drv_priv = ctx; |
| ctx->vb2_q.mem_ops = &msm_fd_vb2_mem_ops; |
| ctx->vb2_q.ops = &msm_fd_vb2_q_ops; |
| ctx->vb2_q.buf_struct_size = sizeof(struct msm_fd_buffer); |
| ctx->vb2_q.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; |
| ctx->vb2_q.io_modes = VB2_USERPTR; |
| ctx->vb2_q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| mutex_init(&ctx->lock); |
| ret = vb2_queue_init(&ctx->vb2_q); |
| if (ret < 0) { |
| dev_err(device->dev, "Error queue init\n"); |
| goto error_vb2_queue_init; |
| } |
| |
| ctx->mem_pool.fd_device = ctx->fd_device; |
| ctx->stats = vmalloc(sizeof(*ctx->stats) * MSM_FD_MAX_RESULT_BUFS); |
| if (!ctx->stats) { |
| dev_err(device->dev, "No memory for face statistics\n"); |
| ret = -ENOMEM; |
| goto error_stats_vmalloc; |
| } |
| |
| ret = cam_config_ahb_clk(NULL, 0, CAM_AHB_CLIENT_FD, |
| CAM_AHB_SVS_VOTE); |
| if (ret < 0) { |
| pr_err("%s: failed to vote for AHB\n", __func__); |
| goto error_ahb_config; |
| } |
| |
| /* Register with CPP VBIF error handler */ |
| msm_cpp_vbif_register_error_handler((void *)ctx, |
| VBIF_CLIENT_FD, msm_fd_vbif_error_handler); |
| |
| return 0; |
| |
| error_ahb_config: |
| vfree(ctx->stats); |
| error_stats_vmalloc: |
| vb2_queue_release(&ctx->vb2_q); |
| error_vb2_queue_init: |
| v4l2_fh_del(&ctx->fh); |
| v4l2_fh_exit(&ctx->fh); |
| kfree(ctx); |
| return ret; |
| } |
| |
| /* |
| * msm_fd_release - Fd device release method. |
| * @file: Pointer to file struct. |
| */ |
| static int msm_fd_release(struct file *file) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(file->private_data); |
| |
| /* Un-register with CPP VBIF error handler */ |
| msm_cpp_vbif_register_error_handler((void *)ctx, |
| VBIF_CLIENT_FD, NULL); |
| |
| mutex_lock(&ctx->lock); |
| vb2_queue_release(&ctx->vb2_q); |
| mutex_unlock(&ctx->lock); |
| |
| vfree(ctx->stats); |
| |
| if (ctx->work_buf.fd != -1) |
| msm_fd_hw_unmap_buffer(&ctx->work_buf); |
| |
| v4l2_fh_del(&ctx->fh); |
| v4l2_fh_exit(&ctx->fh); |
| |
| kfree(ctx); |
| |
| if (cam_config_ahb_clk(NULL, 0, CAM_AHB_CLIENT_FD, |
| CAM_AHB_SUSPEND_VOTE) < 0) |
| pr_err("%s: failed to remove vote for AHB\n", __func__); |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_poll - Fd device pool method. |
| * @file: Pointer to file struct. |
| * @wait: Pointer to pool table struct. |
| */ |
| static unsigned int msm_fd_poll(struct file *file, |
| struct poll_table_struct *wait) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(file->private_data); |
| unsigned int ret; |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_poll(&ctx->vb2_q, file, wait); |
| mutex_unlock(&ctx->lock); |
| |
| if (atomic_read(&ctx->subscribed_for_event)) { |
| poll_wait(file, &ctx->fh.wait, wait); |
| if (v4l2_event_pending(&ctx->fh)) |
| ret |= POLLPRI; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * msm_fd_private_ioctl - V4l2 private ioctl handler. |
| * @file: Pointer to file struct. |
| * @fd: V4l2 device file handle. |
| * @valid_prio: Priority ioctl valid flag. |
| * @cmd: Ioctl command. |
| * @arg: Ioctl argument. |
| */ |
| static long msm_fd_private_ioctl(struct file *file, void *fh, |
| bool valid_prio, unsigned int cmd, void *arg) |
| { |
| struct msm_fd_result *req_result = arg; |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| struct msm_fd_stats *stats; |
| int stats_idx; |
| int ret = 0; |
| int i; |
| |
| switch (cmd) { |
| case VIDIOC_MSM_FD_GET_RESULT: |
| if (req_result->frame_id == 0) { |
| dev_err(ctx->fd_device->dev, "Invalid frame id\n"); |
| return -EINVAL; |
| } |
| |
| stats_idx = req_result->frame_id % MSM_FD_MAX_RESULT_BUFS; |
| stats = &ctx->stats[stats_idx]; |
| if (req_result->frame_id != atomic_read(&stats->frame_id)) { |
| dev_err(ctx->fd_device->dev, "Stats not available\n"); |
| return -EINVAL; |
| } |
| |
| if (req_result->face_cnt > stats->face_cnt) |
| req_result->face_cnt = stats->face_cnt; |
| |
| for (i = 0; i < req_result->face_cnt; i++) { |
| ret = copy_to_user((void __user *) |
| &req_result->face_data[i], |
| &stats->face_data[i], |
| sizeof(struct msm_fd_face_data)); |
| if (ret) { |
| dev_err(ctx->fd_device->dev, "Copy to user\n"); |
| return -EFAULT; |
| } |
| } |
| |
| if (req_result->frame_id != atomic_read(&stats->frame_id)) { |
| dev_err(ctx->fd_device->dev, "Erroneous buffer\n"); |
| return -EINVAL; |
| } |
| break; |
| default: |
| dev_err(ctx->fd_device->dev, "Wrong ioctl type %x\n", cmd); |
| ret = -ENOTTY; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_COMPAT |
| /* |
| * msm_fd_compat_ioctl32 - Compat ioctl handler function. |
| * @file: Pointer to file struct. |
| * @cmd: Ioctl command. |
| * @arg: Ioctl argument. |
| */ |
| static long msm_fd_compat_ioctl32(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| long ret; |
| |
| switch (cmd) { |
| case VIDIOC_MSM_FD_GET_RESULT32: |
| { |
| struct msm_fd_result32 result32; |
| struct msm_fd_result result; |
| |
| if (copy_from_user(&result32, (void __user *)arg, |
| sizeof(result32))) |
| return -EFAULT; |
| |
| result.frame_id = result32.frame_id; |
| result.face_cnt = result32.face_cnt; |
| result.face_data = compat_ptr(result32.face_data); |
| |
| ret = msm_fd_private_ioctl(file, file->private_data, |
| 0, VIDIOC_MSM_FD_GET_RESULT, (void *)&result); |
| |
| result32.frame_id = result.frame_id; |
| result32.face_cnt = result.face_cnt; |
| |
| if (copy_to_user((void __user *)arg, &result32, |
| sizeof(result32))) |
| return -EFAULT; |
| |
| break; |
| } |
| default: |
| ret = -ENOIOCTLCMD; |
| break; |
| |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /* Fd device file operations callbacks */ |
| static const struct v4l2_file_operations fd_fops = { |
| .owner = THIS_MODULE, |
| .open = msm_fd_open, |
| .release = msm_fd_release, |
| .poll = msm_fd_poll, |
| .unlocked_ioctl = video_ioctl2, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl32 = msm_fd_compat_ioctl32, |
| #endif |
| }; |
| |
| /* |
| * msm_fd_querycap - V4l2 ioctl query capability handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @cap: Pointer to v4l2_capability struct need to be filled. |
| */ |
| static int msm_fd_querycap(struct file *file, |
| void *fh, struct v4l2_capability *cap) |
| { |
| cap->bus_info[0] = 0; |
| strlcpy(cap->driver, MSM_FD_DRV_NAME, sizeof(cap->driver)); |
| strlcpy(cap->card, MSM_FD_DRV_NAME, sizeof(cap->card)); |
| cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_enum_fmt_vid_out - V4l2 ioctl enumerate format handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @f: Pointer to v4l2_fmtdesc struct need to be filled. |
| */ |
| static int msm_fd_enum_fmt_vid_out(struct file *file, |
| void *fh, struct v4l2_fmtdesc *f) |
| { |
| if (f->index > 0) |
| return -EINVAL; |
| |
| f->pixelformat = V4L2_PIX_FMT_GREY; |
| strlcpy(f->description, "8 Greyscale", |
| sizeof(f->description)); |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_g_fmt - V4l2 ioctl get format handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @f: Pointer to v4l2_format struct need to be filled. |
| */ |
| static int msm_fd_g_fmt(struct file *file, void *fh, struct v4l2_format *f) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| return msm_fd_fill_format_from_ctx(f, ctx); |
| } |
| |
| /* |
| * msm_fd_try_fmt_vid_out - V4l2 ioctl try format handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @f: Pointer to v4l2_format struct. |
| */ |
| static int msm_fd_try_fmt_vid_out(struct file *file, |
| void *fh, struct v4l2_format *f) |
| { |
| int index; |
| |
| index = msm_fd_get_format_index(f); |
| |
| return msm_fd_fill_format_from_index(f, index); |
| } |
| |
| /* |
| * msm_fd_s_fmt_vid_out - V4l2 ioctl set format handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @f: Pointer to v4l2_format struct. |
| */ |
| static int msm_fd_s_fmt_vid_out(struct file *file, |
| void *fh, struct v4l2_format *f) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int index; |
| |
| index = msm_fd_get_format_index(f); |
| |
| msm_fd_fill_format_from_index(f, index); |
| |
| ctx->format.size = &fd_size[index]; |
| ctx->format.pixelformat = f->fmt.pix.pixelformat; |
| ctx->format.bytesperline = f->fmt.pix.bytesperline; |
| ctx->format.sizeimage = f->fmt.pix.sizeimage; |
| |
| /* Initialize crop */ |
| ctx->format.crop.top = 0; |
| ctx->format.crop.left = 0; |
| ctx->format.crop.width = fd_size[index].width; |
| ctx->format.crop.height = fd_size[index].height; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_reqbufs - V4l2 ioctl request buffers handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @req: Pointer to v4l2_requestbuffer struct. |
| */ |
| static int msm_fd_reqbufs(struct file *file, |
| void *fh, struct v4l2_requestbuffers *req) |
| { |
| int ret; |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_reqbufs(&ctx->vb2_q, req); |
| mutex_unlock(&ctx->lock); |
| return ret; |
| } |
| |
| /* |
| * msm_fd_qbuf - V4l2 ioctl queue buffer handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @pb: Pointer to v4l2_buffer struct. |
| */ |
| static int msm_fd_qbuf(struct file *file, void *fh, |
| struct v4l2_buffer *pb) |
| { |
| int ret; |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_qbuf(&ctx->vb2_q, pb); |
| mutex_unlock(&ctx->lock); |
| return ret; |
| |
| } |
| |
| /* |
| * msm_fd_dqbuf - V4l2 ioctl dequeue buffer handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @pb: Pointer to v4l2_buffer struct. |
| */ |
| static int msm_fd_dqbuf(struct file *file, |
| void *fh, struct v4l2_buffer *pb) |
| { |
| int ret; |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_dqbuf(&ctx->vb2_q, pb, file->f_flags & O_NONBLOCK); |
| mutex_unlock(&ctx->lock); |
| return ret; |
| } |
| |
| /* |
| * msm_fd_streamon - V4l2 ioctl stream on handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @buf_type: V4l2 buffer type. |
| */ |
| static int msm_fd_streamon(struct file *file, |
| void *fh, enum v4l2_buf_type buf_type) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int ret; |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_streamon(&ctx->vb2_q, buf_type); |
| mutex_unlock(&ctx->lock); |
| if (ret < 0) |
| dev_err(ctx->fd_device->dev, "Stream on fails\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * msm_fd_streamoff - V4l2 ioctl stream off handler. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @buf_type: V4l2 buffer type. |
| */ |
| static int msm_fd_streamoff(struct file *file, |
| void *fh, enum v4l2_buf_type buf_type) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int ret; |
| |
| mutex_lock(&ctx->lock); |
| ret = vb2_streamoff(&ctx->vb2_q, buf_type); |
| mutex_unlock(&ctx->lock); |
| if (ret < 0) |
| dev_err(ctx->fd_device->dev, "Stream off fails\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * msm_fd_subscribe_event - V4l2 ioctl subscribe for event handler. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_event_subscription containing event information. |
| */ |
| static int msm_fd_subscribe_event(struct v4l2_fh *fh, |
| const struct v4l2_event_subscription *sub) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int ret; |
| |
| if (sub->type != MSM_EVENT_FD) |
| return -EINVAL; |
| |
| ret = v4l2_event_subscribe(fh, sub, MSM_FD_MAX_RESULT_BUFS, NULL); |
| if (!ret) |
| atomic_set(&ctx->subscribed_for_event, 1); |
| |
| return ret; |
| } |
| |
| /* |
| * msm_fd_unsubscribe_event - V4l2 ioctl unsubscribe from event handler. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_event_subscription containing event information. |
| */ |
| static int msm_fd_unsubscribe_event(struct v4l2_fh *fh, |
| const struct v4l2_event_subscription *sub) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int ret; |
| |
| ret = v4l2_event_unsubscribe(fh, sub); |
| if (!ret) |
| atomic_set(&ctx->subscribed_for_event, 0); |
| |
| return ret; |
| } |
| |
| /* |
| * msm_fd_guery_ctrl - V4l2 ioctl query control. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_queryctrl struct info need to be filled based on id. |
| */ |
| static int msm_fd_guery_ctrl(struct file *file, void *fh, |
| struct v4l2_queryctrl *a) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| switch (a->id) { |
| case V4L2_CID_FD_SPEED: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = ctx->fd_device->clk_rates_num; |
| a->minimum = 0; |
| a->maximum = ctx->fd_device->clk_rates_num; |
| a->step = 1; |
| strlcpy(a->name, "msm fd face speed idx", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_FACE_ANGLE: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = msm_fd_angle[MSM_FD_DEF_ANGLE_IDX]; |
| a->minimum = msm_fd_angle[0]; |
| a->maximum = msm_fd_angle[ARRAY_SIZE(msm_fd_angle) - 1]; |
| a->step = 1; |
| strlcpy(a->name, "msm fd face angle ctrl", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_FACE_DIRECTION: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = msm_fd_dir[MSM_FD_DEF_DIR_IDX]; |
| a->minimum = msm_fd_dir[0]; |
| a->maximum = msm_fd_dir[ARRAY_SIZE(msm_fd_dir) - 1]; |
| a->step = 1; |
| strlcpy(a->name, "msm fd face direction ctrl", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_MIN_FACE_SIZE: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = msm_fd_min_size[MSM_FD_DEF_MIN_SIZE_IDX]; |
| a->minimum = msm_fd_min_size[0]; |
| a->maximum = msm_fd_min_size[ARRAY_SIZE(msm_fd_min_size) - 1]; |
| a->step = 1; |
| strlcpy(a->name, "msm fd minimum face size (pixels)", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_DETECTION_THRESHOLD: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = MSM_FD_DEF_THRESHOLD; |
| a->minimum = 0; |
| a->maximum = MSM_FD_MAX_THRESHOLD_VALUE; |
| a->step = 1; |
| strlcpy(a->name, "msm fd detection threshold", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_SIZE: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = fd_size[0].work_size; |
| a->minimum = fd_size[(ARRAY_SIZE(fd_size) - 1)].work_size; |
| a->maximum = fd_size[0].work_size; |
| a->step = 1; |
| strlcpy(a->name, "msm fd working memory size", |
| sizeof(a->name)); |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_FD: |
| a->type = V4L2_CTRL_TYPE_INTEGER; |
| a->default_value = -1; |
| a->minimum = 0; |
| a->maximum = INT_MAX; |
| a->step = 1; |
| strlcpy(a->name, "msm fd ion fd of working memory", |
| sizeof(a->name)); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_g_ctrl - V4l2 ioctl get control. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_queryctrl struct need to be filled. |
| */ |
| static int msm_fd_g_ctrl(struct file *file, void *fh, struct v4l2_control *a) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| switch (a->id) { |
| case V4L2_CID_FD_SPEED: |
| a->value = ctx->settings.speed; |
| break; |
| case V4L2_CID_FD_FACE_ANGLE: |
| a->value = msm_fd_angle[ctx->settings.angle_index]; |
| break; |
| case V4L2_CID_FD_FACE_DIRECTION: |
| a->value = msm_fd_dir[ctx->settings.direction_index]; |
| break; |
| case V4L2_CID_FD_MIN_FACE_SIZE: |
| a->value = msm_fd_min_size[ctx->settings.min_size_index]; |
| break; |
| case V4L2_CID_FD_DETECTION_THRESHOLD: |
| a->value = ctx->settings.threshold; |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_SIZE: |
| if (!ctx->format.size) |
| return -EINVAL; |
| |
| a->value = ctx->format.size->work_size; |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_FD: |
| if (ctx->work_buf.fd == -1) |
| return -EINVAL; |
| |
| a->value = ctx->work_buf.fd; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_s_ctrl - V4l2 ioctl set control. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_queryctrl struct need to be set. |
| */ |
| static int msm_fd_s_ctrl(struct file *file, void *fh, struct v4l2_control *a) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int idx; |
| int ret; |
| |
| switch (a->id) { |
| case V4L2_CID_FD_SPEED: |
| if (a->value > ctx->fd_device->clk_rates_num - 1) |
| a->value = ctx->fd_device->clk_rates_num - 1; |
| else if (a->value < 0) |
| a->value = 0; |
| |
| ctx->settings.speed = a->value; |
| break; |
| case V4L2_CID_FD_FACE_ANGLE: |
| idx = msm_fd_get_idx_from_value(a->value, msm_fd_angle, |
| ARRAY_SIZE(msm_fd_angle)); |
| |
| ctx->settings.angle_index = idx; |
| a->value = msm_fd_angle[ctx->settings.angle_index]; |
| break; |
| case V4L2_CID_FD_FACE_DIRECTION: |
| idx = msm_fd_get_idx_from_value(a->value, msm_fd_dir, |
| ARRAY_SIZE(msm_fd_dir)); |
| |
| ctx->settings.direction_index = idx; |
| a->value = msm_fd_dir[ctx->settings.direction_index]; |
| break; |
| case V4L2_CID_FD_MIN_FACE_SIZE: |
| idx = msm_fd_get_idx_from_value(a->value, msm_fd_min_size, |
| ARRAY_SIZE(msm_fd_min_size)); |
| |
| ctx->settings.min_size_index = idx; |
| a->value = msm_fd_min_size[ctx->settings.min_size_index]; |
| break; |
| case V4L2_CID_FD_DETECTION_THRESHOLD: |
| if (a->value > MSM_FD_MAX_THRESHOLD_VALUE) |
| a->value = MSM_FD_MAX_THRESHOLD_VALUE; |
| else if (a->value < 0) |
| a->value = 0; |
| |
| ctx->settings.threshold = a->value; |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_SIZE: |
| if (!ctx->format.size) |
| return -EINVAL; |
| |
| if (a->value < ctx->format.size->work_size) |
| a->value = ctx->format.size->work_size; |
| break; |
| case V4L2_CID_FD_WORK_MEMORY_FD: |
| mutex_lock(&ctx->fd_device->recovery_lock); |
| if (ctx->work_buf.fd != -1) |
| msm_fd_hw_unmap_buffer(&ctx->work_buf); |
| if (a->value >= 0) { |
| ret = msm_fd_hw_map_buffer(&ctx->mem_pool, |
| a->value, &ctx->work_buf); |
| if (ret < 0) { |
| mutex_unlock(&ctx->fd_device->recovery_lock); |
| return ret; |
| } |
| } |
| mutex_unlock(&ctx->fd_device->recovery_lock); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_cropcap - V4l2 ioctl crop capabilities. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_cropcap struct need to be set. |
| */ |
| static int msm_fd_cropcap(struct file *file, void *fh, struct v4l2_cropcap *a) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| if (!ctx->format.size) { |
| dev_err(ctx->fd_device->dev, "Cropcap fails format missing\n"); |
| return -EINVAL; |
| } |
| |
| a->bounds.top = 0; |
| a->bounds.left = 0; |
| a->bounds.width = ctx->format.size->width; |
| a->bounds.height = ctx->format.size->height; |
| |
| a->defrect = ctx->format.crop; |
| |
| a->pixelaspect.numerator = 1; |
| a->pixelaspect.denominator = 1; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_g_crop - V4l2 ioctl get crop. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_crop struct need to be set. |
| */ |
| static int msm_fd_g_crop(struct file *file, void *fh, struct v4l2_crop *crop) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| |
| if (!ctx->format.size) { |
| dev_err(ctx->fd_device->dev, "Get crop, format missing!\n"); |
| return -EINVAL; |
| } |
| |
| crop->c = ctx->format.crop; |
| |
| return 0; |
| } |
| |
| /* |
| * msm_fd_s_crop - V4l2 ioctl set crop. |
| * @file: Pointer to file struct. |
| * @fh: V4l2 File handle. |
| * @sub: Pointer to v4l2_crop struct need to be set. |
| */ |
| static int msm_fd_s_crop(struct file *file, void *fh, |
| const struct v4l2_crop *crop) |
| { |
| struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); |
| int min_face_size; |
| |
| if (!ctx->format.size) { |
| dev_err(ctx->fd_device->dev, "Get crop, format missing!\n"); |
| return -EINVAL; |
| } |
| |
| /* First check that crop is valid */ |
| min_face_size = msm_fd_min_size[ctx->settings.min_size_index]; |
| |
| if (crop->c.width < min_face_size || crop->c.height < min_face_size) |
| return -EINVAL; |
| |
| if (crop->c.width + crop->c.left > ctx->format.size->width) |
| return -EINVAL; |
| |
| if (crop->c.height + crop->c.top > ctx->format.size->height) |
| return -EINVAL; |
| |
| ctx->format.crop = crop->c; |
| |
| return 0; |
| } |
| |
| /* V4l2 ioctl handlers */ |
| static const struct v4l2_ioctl_ops fd_ioctl_ops = { |
| .vidioc_querycap = msm_fd_querycap, |
| .vidioc_enum_fmt_vid_out = msm_fd_enum_fmt_vid_out, |
| .vidioc_g_fmt_vid_out = msm_fd_g_fmt, |
| .vidioc_try_fmt_vid_out = msm_fd_try_fmt_vid_out, |
| .vidioc_s_fmt_vid_out = msm_fd_s_fmt_vid_out, |
| .vidioc_reqbufs = msm_fd_reqbufs, |
| .vidioc_qbuf = msm_fd_qbuf, |
| .vidioc_dqbuf = msm_fd_dqbuf, |
| .vidioc_streamon = msm_fd_streamon, |
| .vidioc_streamoff = msm_fd_streamoff, |
| .vidioc_queryctrl = msm_fd_guery_ctrl, |
| .vidioc_s_ctrl = msm_fd_s_ctrl, |
| .vidioc_g_ctrl = msm_fd_g_ctrl, |
| .vidioc_cropcap = msm_fd_cropcap, |
| .vidioc_g_crop = msm_fd_g_crop, |
| .vidioc_s_crop = msm_fd_s_crop, |
| .vidioc_subscribe_event = msm_fd_subscribe_event, |
| .vidioc_unsubscribe_event = msm_fd_unsubscribe_event, |
| .vidioc_default = msm_fd_private_ioctl, |
| }; |
| |
| /* |
| * msm_fd_fill_results - Read and fill face detection result. |
| * @fd: Pointer to fd device. |
| * @face: Pointer of face data which information need to be stored. |
| * @idx: Face number index need to be filled. |
| */ |
| static void msm_fd_fill_results(struct msm_fd_device *fd, |
| struct msm_fd_face_data *face, int idx) |
| { |
| int half_face_size; |
| |
| msm_fd_hw_get_result_angle_pose(fd, idx, &face->angle, &face->pose); |
| |
| msm_fd_hw_get_result_conf_size(fd, idx, &face->confidence, |
| &face->face.width); |
| face->face.height = face->face.width; |
| |
| face->face.left = msm_fd_hw_get_result_x(fd, idx); |
| face->face.top = msm_fd_hw_get_result_y(fd, idx); |
| |
| half_face_size = (face->face.width >> 1); |
| if (face->face.left > half_face_size) |
| face->face.left -= half_face_size; |
| else |
| face->face.left = 0; |
| |
| half_face_size = (face->face.height >> 1); |
| if (face->face.top > half_face_size) |
| face->face.top -= half_face_size; |
| else |
| face->face.top = 0; |
| } |
| |
| /* |
| * msm_fd_wq_handler - Fd device workqueue handler. |
| * @work: Pointer to work struct. |
| * |
| * This function is bottom half of fd irq what it does: |
| * |
| * - Stop the fd engine. |
| * - Getter fd result and store in stats buffer. |
| * - If available schedule next buffer for processing. |
| * - Sent event to v4l2. |
| * - Release buffer from v4l2 queue. |
| */ |
| static void msm_fd_wq_handler(struct work_struct *work) |
| { |
| struct msm_fd_buffer *active_buf; |
| struct msm_fd_stats *stats; |
| struct msm_fd_event *fd_event; |
| struct msm_fd_device *fd; |
| struct fd_ctx *ctx; |
| struct v4l2_event event; |
| int i; |
| |
| fd = container_of(work, struct msm_fd_device, work); |
| MSM_FD_SPIN_LOCK(fd->slock, 1); |
| active_buf = msm_fd_hw_get_active_buffer(fd, 0); |
| if (!active_buf) { |
| /* This should never happen, something completely wrong */ |
| dev_err(fd->dev, "Oops no active buffer empty queue\n"); |
| MSM_FD_SPIN_UNLOCK(fd->slock, 1); |
| return; |
| } |
| ctx = vb2_get_drv_priv(active_buf->vb_v4l2_buf.vb2_buf.vb2_queue); |
| |
| /* Increment sequence number, 0 means sequence is not valid */ |
| ctx->sequence++; |
| if (unlikely(!ctx->sequence)) |
| ctx->sequence = 1; |
| |
| /* Fill face detection statistics */ |
| stats = &ctx->stats[ctx->sequence % MSM_FD_MAX_RESULT_BUFS]; |
| |
| /* First mark stats as invalid */ |
| atomic_set(&stats->frame_id, 0); |
| |
| stats->face_cnt = msm_fd_hw_get_face_count(fd); |
| for (i = 0; i < stats->face_cnt; i++) |
| msm_fd_fill_results(fd, &stats->face_data[i], i); |
| |
| /* Stats are ready, set correct frame id */ |
| atomic_set(&stats->frame_id, ctx->sequence); |
| |
| /* If Recovery mode is on, we got IRQ after recovery, reset it */ |
| if (fd->recovery_mode) { |
| fd->recovery_mode = 0; |
| dev_dbg(fd->dev, "Got IRQ after Recovery\n"); |
| } |
| |
| if (fd->state == MSM_FD_DEVICE_RUNNING) { |
| /* We have the data from fd hw, we can start next processing */ |
| msm_fd_hw_schedule_next_buffer(fd, 0); |
| } |
| |
| /* Return buffer to vb queue */ |
| active_buf->vb_v4l2_buf.sequence = ctx->fh.sequence; |
| vb2_buffer_done(&active_buf->vb_v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE); |
| |
| /* Sent event */ |
| memset(&event, 0x00, sizeof(event)); |
| event.type = MSM_EVENT_FD; |
| fd_event = (struct msm_fd_event *)event.u.data; |
| fd_event->face_cnt = stats->face_cnt; |
| fd_event->buf_index = active_buf->vb_v4l2_buf.vb2_buf.index; |
| fd_event->frame_id = ctx->sequence; |
| v4l2_event_queue_fh(&ctx->fh, &event); |
| |
| /* Release buffer from the device */ |
| msm_fd_hw_buffer_done(fd, active_buf, 0); |
| |
| MSM_FD_SPIN_UNLOCK(fd->slock, 1); |
| } |
| |
| /* |
| * fd_probe - Fd device probe method. |
| * @pdev: Pointer fd platform device. |
| */ |
| static int fd_probe(struct platform_device *pdev) |
| { |
| struct msm_fd_device *fd; |
| int ret; |
| int i; |
| |
| /* Face detection device struct */ |
| fd = kzalloc(sizeof(struct msm_fd_device), GFP_KERNEL); |
| if (!fd) |
| return -ENOMEM; |
| |
| mutex_init(&fd->lock); |
| spin_lock_init(&fd->slock); |
| mutex_init(&fd->recovery_lock); |
| init_completion(&fd->hw_halt_completion); |
| INIT_LIST_HEAD(&fd->buf_queue); |
| fd->pdev = pdev; |
| fd->dev = &pdev->dev; |
| |
| /* Get resources */ |
| ret = msm_fd_hw_get_mem_resources(pdev, fd); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail get resources\n"); |
| ret = -ENODEV; |
| goto error_mem_resources; |
| } |
| |
| ret = msm_camera_get_regulator_info(pdev, &fd->vdd_info, |
| &fd->num_reg); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail to get regulators\n"); |
| goto error_get_regulator; |
| } |
| ret = msm_camera_get_clk_info_and_rates(pdev, &fd->clk_info, |
| &fd->clk, &fd->clk_rates, &fd->clk_rates_num, &fd->clk_num); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail to get clocks\n"); |
| goto error_get_clocks; |
| } |
| |
| /*set memcore and mem periphery logic flags to 0*/ |
| for (i = 0; i < fd->clk_num; i++) { |
| if ((strcmp(fd->clk_info[i].clk_name, |
| "mmss_fd_core_clk") == 0) || |
| (strcmp(fd->clk_info[i].clk_name, |
| "mmss_fd_core_uar_clk") == 0)) { |
| msm_camera_set_clk_flags(fd->clk[i], |
| CLKFLAG_NORETAIN_MEM); |
| msm_camera_set_clk_flags(fd->clk[i], |
| CLKFLAG_NORETAIN_PERIPH); |
| } |
| } |
| |
| ret = msm_camera_register_bus_client(pdev, CAM_BUS_CLIENT_FD); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail to get bus\n"); |
| goto error_get_bus; |
| } |
| |
| /* Get face detect hw before read engine revision */ |
| ret = msm_fd_hw_get(fd, 0); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail to get hw\n"); |
| goto error_hw_get_request_irq; |
| } |
| fd->hw_revision = msm_fd_hw_get_revision(fd); |
| |
| msm_fd_hw_put(fd); |
| |
| ret = msm_fd_hw_request_irq(pdev, fd, msm_fd_wq_handler); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Fail request irq\n"); |
| goto error_hw_get_request_irq; |
| } |
| |
| /* v4l2 device */ |
| ret = v4l2_device_register(&pdev->dev, &fd->v4l2_dev); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "Failed to register v4l2 device\n"); |
| ret = -ENOENT; |
| goto error_v4l2_register; |
| } |
| |
| fd->video.fops = &fd_fops; |
| fd->video.ioctl_ops = &fd_ioctl_ops; |
| fd->video.minor = -1; |
| fd->video.release = video_device_release; |
| fd->video.v4l2_dev = &fd->v4l2_dev; |
| fd->video.vfl_dir = VFL_DIR_TX; |
| fd->video.vfl_type = VFL_TYPE_GRABBER; |
| strlcpy(fd->video.name, MSM_FD_DRV_NAME, sizeof(fd->video.name)); |
| |
| ret = video_register_device(&fd->video, VFL_TYPE_GRABBER, -1); |
| if (ret < 0) { |
| v4l2_err(&fd->v4l2_dev, "Failed to register video device\n"); |
| goto error_video_register; |
| } |
| |
| video_set_drvdata(&fd->video, fd); |
| |
| platform_set_drvdata(pdev, fd); |
| |
| return 0; |
| |
| error_video_register: |
| v4l2_device_unregister(&fd->v4l2_dev); |
| error_v4l2_register: |
| msm_fd_hw_release_irq(fd); |
| error_hw_get_request_irq: |
| msm_camera_unregister_bus_client(CAM_BUS_CLIENT_FD); |
| error_get_bus: |
| msm_camera_put_clk_info_and_rates(pdev, &fd->clk_info, |
| &fd->clk, &fd->clk_rates, fd->clk_rates_num, fd->clk_num); |
| error_get_clocks: |
| msm_camera_put_regulators(pdev, &fd->vdd_info, fd->num_reg); |
| error_get_regulator: |
| msm_fd_hw_release_mem_resources(fd); |
| error_mem_resources: |
| kfree(fd); |
| return ret; |
| } |
| |
| /* |
| * fd_device_remove - Fd device remove method. |
| * @pdev: Pointer fd platform device. |
| */ |
| static int fd_device_remove(struct platform_device *pdev) |
| { |
| struct msm_fd_device *fd; |
| |
| fd = platform_get_drvdata(pdev); |
| if (fd == NULL) { |
| dev_err(&pdev->dev, "Can not get fd drvdata\n"); |
| return 0; |
| } |
| video_unregister_device(&fd->video); |
| v4l2_device_unregister(&fd->v4l2_dev); |
| msm_fd_hw_release_irq(fd); |
| msm_camera_unregister_bus_client(CAM_BUS_CLIENT_FD); |
| msm_camera_put_clk_info_and_rates(pdev, &fd->clk_info, |
| &fd->clk, &fd->clk_rates, fd->clk_rates_num, fd->clk_num); |
| msm_camera_put_regulators(pdev, &fd->vdd_info, fd->num_reg); |
| msm_fd_hw_release_mem_resources(fd); |
| kfree(fd); |
| |
| return 0; |
| } |
| |
| /* Device tree match struct */ |
| static const struct of_device_id msm_fd_dt_match[] = { |
| {.compatible = "qcom,face-detection"}, |
| {} |
| }; |
| |
| /* Fd platform driver definition */ |
| static struct platform_driver fd_driver = { |
| .probe = fd_probe, |
| .remove = fd_device_remove, |
| .driver = { |
| .name = MSM_FD_DRV_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = msm_fd_dt_match, |
| }, |
| }; |
| |
| static int __init msm_fd_init_module(void) |
| { |
| return platform_driver_register(&fd_driver); |
| } |
| |
| static void __exit msm_fd_exit_module(void) |
| { |
| platform_driver_unregister(&fd_driver); |
| } |
| |
| module_init(msm_fd_init_module); |
| module_exit(msm_fd_exit_module); |
| MODULE_DESCRIPTION("MSM FD driver"); |
| MODULE_LICENSE("GPL v2"); |