From 2fb71e29c3166f968b267becd2f2307e4abd9634 Mon Sep 17 00:00:00 2001 From: Gjorgji Rosikopulos Date: Thu, 27 Mar 2014 16:35:15 -0700 Subject: [PATCH] msm: camera: MSM FD face detection driver MSM FD (Face detection) driver - Detects faces from a given input buffer. The driver implements standard V4L2 interface, and supports V4L2 event interface. Uses standard V4L2 IOCTLs and controls. However there is one private IOCTL used to obtain the result of the driver: "VIDIOC_MSM_FD_GET_RESULT" Compat support is also added for this IOCTL. The driver can operate simultaneously with multiple open file descriptors. Change-Id: I50ebdd9c966c18dec27a06630a0cbd0affc0b1bb Signed-off-by: Gjorgji Rosikopulos --- .../bindings/media/video/msm-fd.txt | 52 + .../media/platform/msm/camera_v2/fd/Makefile | 4 + .../platform/msm/camera_v2/fd/msm_fd_dev.c | 1383 +++++++++++++++++ .../platform/msm/camera_v2/fd/msm_fd_dev.h | 238 +++ .../platform/msm/camera_v2/fd/msm_fd_hw.c | 1000 ++++++++++++ .../platform/msm/camera_v2/fd/msm_fd_hw.h | 69 + .../platform/msm/camera_v2/fd/msm_fd_regs.h | 148 ++ include/media/msm_fd.h | 17 + include/uapi/media/msm_fd.h | 104 ++ 9 files changed, 3015 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/video/msm-fd.txt create mode 100644 drivers/media/platform/msm/camera_v2/fd/Makefile create mode 100644 drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c create mode 100644 drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.h create mode 100644 drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.c create mode 100644 drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.h create mode 100644 drivers/media/platform/msm/camera_v2/fd/msm_fd_regs.h create mode 100644 include/media/msm_fd.h create mode 100644 include/uapi/media/msm_fd.h diff --git a/Documentation/devicetree/bindings/media/video/msm-fd.txt b/Documentation/devicetree/bindings/media/video/msm-fd.txt new file mode 100644 index 000000000000..30d01fbcd311 --- /dev/null +++ b/Documentation/devicetree/bindings/media/video/msm-fd.txt @@ -0,0 +1,52 @@ +* Qualcomm MSM FD + +Face detection hardware block. +The Face Detection Hardware Block will offload processing +on the host and also reduce power consumption. +Supports: +Front and back camera face detection concurrently. +Sizes: QVGA, VGA, WQVGA, WVGA at 20 pix minimum face size. + +Required properties: + +- compatible: + - "qcom,face-detection" +- reg: offset and length of the register set for the device. +- reg-names: should specify relevant names to each reg property defined. + - "fd_core" - FD CORE hardware register set. + - "fd_misc" - FD MISC hardware register set. + - "fd_vbif" - FD VBIF hardware register set. +- interrupts: should contain the fd interrupts. +- interrupt-names: should specify relevant names to each interrupts + property defined. +- vdd-supply: phandle to GDSC regulator controlling face detection hw. +- clocks: list of entries each of which contains: + - phandle to the clock controller. + - macro containing clock's name in hardware. +- clock-names: should specify relevant names to each clocks + property defined. + +Optional properties: + +- clock-rates: should specify clock rates in Hz to each clocks + property defined. + +Example: + + qcom,fd@fd878000 { + compatible = "qcom,face-detection"; + reg = <0xfd878000 0x800>, + <0xfd87c000 0x800>, + <0xfd860000 0x1000>; + reg-names = "fd_core", "fd_misc", "fd_vbif"; + interrupts = <0 316 0>; + interrupt-names = "fd"; + vdd-supply = <&gdsc_fd>; + clocks = <&clock_mmss clk_fd_core_clk>, + <&clock_mmss clk_fd_ahb_clk>, + <&clock_mmss clk_fd_axi_clk>, + <&clock_mmss clk_fd_core_uar_clk>; + clock-names = "fd_core_clk", "fd_core_uar_clk", + "fd_axi_clk", "fd_ahb_clk"; + clock-rates = <400 400 334 80 240>; + }; diff --git a/drivers/media/platform/msm/camera_v2/fd/Makefile b/drivers/media/platform/msm/camera_v2/fd/Makefile new file mode 100644 index 000000000000..cd94a6c779d2 --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/Makefile @@ -0,0 +1,4 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +ccflags-y += -Idrivers/media/video/msm + +obj-$(CONFIG_MSM_FD) += msm_fd_dev.o msm_fd_hw.o diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c new file mode 100644 index 000000000000..0c9599d18e7e --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.c @@ -0,0 +1,1383 @@ +/* Copyright (c) 2014, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + + for (index = 0; index < array_size; index++) { + if (value <= array[index]) + return index; + } + return index - 1; +} + +/* + * 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 (NULL == c->format.size) + 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 struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct fd_ctx *ctx = vb2_get_drv_priv(q); + + *num_planes = 1; + + if (NULL == fmt) + sizes[0] = ctx->format.sizeimage; + else + sizes[0] = fmt->fmt.pix.sizeimage; + + alloc_ctxs[0] = &ctx->mem_pool; + + return 0; +} + +/* + * msm_fd_buf_init - vb2_ops buf_init callback. + * @vb: Pointer to vb2 buffer struct. + */ +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); + + return; +} + +/* + * 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.handle) { + dev_err(ctx->fd_device->dev, "Missing working buffer\n"); + return -EINVAL; + } + + 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"); + + return ret; +} + +/* + * msm_fd_stop_streaming - vb2_ops stop_streaming callback. + * @q: Pointer to vb2 queue struct. + */ +static int msm_fd_stop_streaming(struct vb2_queue *q) +{ + struct fd_ctx *ctx = vb2_get_drv_priv(q); + + msm_fd_hw_remove_buffers_from_queue(ctx->fd_device, q); + + return 0; +} + +/* 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(void *alloc_ctx, + unsigned long vaddr, unsigned long size, int write) +{ + struct msm_fd_mem_pool *pool = 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_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; + 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_type = V4L2_BUF_FLAG_TIMESTAMP_COPY; + 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.client = msm_ion_client_create(-1, MSM_FD_DRV_NAME); + if (IS_ERR_OR_NULL(ctx->mem_pool.client)) { + dev_err(device->dev, "Error ion client create\n"); + goto error_ion_client_create; + } + ctx->mem_pool.domain_num = ctx->fd_device->iommu_domain_num; + + ret = iommu_attach_device(ctx->fd_device->iommu_domain, + ctx->fd_device->iommu_dev); + if (ret) { + dev_err(device->dev, "Can not attach iommu domain\n"); + goto error_iommu_attach; + } + + 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; + } + + return 0; + +error_stats_vmalloc: + iommu_detach_device(ctx->fd_device->iommu_domain, + ctx->fd_device->iommu_dev); +error_iommu_attach: + ion_client_destroy(ctx->mem_pool.client); +error_ion_client_create: + 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); + + vb2_queue_release(&ctx->vb2_q); + + vfree(ctx->stats); + + if (ctx->work_buf.handle) + msm_fd_hw_unmap_buffer(&ctx->work_buf); + + iommu_detach_device(ctx->fd_device->iommu_domain, + ctx->fd_device->iommu_dev); + ion_client_destroy(ctx->mem_pool.client); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + kfree(ctx); + + 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; + + ret = vb2_poll(&ctx->vb2_q, file, wait); + + 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; + 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) +{ + struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); + + return vb2_reqbufs(&ctx->vb2_q, req); +} + +/* + * 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) +{ + struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); + + return vb2_qbuf(&ctx->vb2_q, pb); +} + +/* + * 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) +{ + struct fd_ctx *ctx = msm_fd_ctx_from_fh(fh); + + return vb2_dqbuf(&ctx->vb2_q, pb, file->f_flags & O_NONBLOCK); +} + +/* + * 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; + + 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 = vb2_streamon(&ctx->vb2_q, buf_type); + if (ret < 0) + dev_err(ctx->fd_device->dev, "Stream on fails\n"); +out: + 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; + + ret = vb2_streamoff(&ctx->vb2_q, buf_type); + if (ret < 0) { + dev_err(ctx->fd_device->dev, "Stream off fails\n"); + goto out; + } + + msm_fd_hw_put(ctx->fd_device); +out: + 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)); + 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.handle) + 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) + a->value = ctx->fd_device->clk_rates_num; + 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: + if (ctx->work_buf.handle) + 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) + return ret; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * msm_fd_cropcap - V4l2 ioctl crop capabilites. + * @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) +{ + 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); + + face->face.left -= (face->face.width >> 1); + face->face.top -= (face->face.height >> 1); +} + +/* + * 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); + + active_buf = msm_fd_hw_get_active_buffer(fd); + if (!active_buf) { + /* This should never happen, something completely wrong */ + dev_err(fd->dev, "Oops no active buffer empty queue\n"); + return; + } + ctx = vb2_get_drv_priv(active_buf->vb.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); + + /* We have the data from fd hw, we can start next processing */ + msm_fd_hw_schedule_next_buffer(fd); + + /* 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.index; + fd_event->frame_id = ctx->sequence; + v4l2_event_queue_fh(&ctx->fh, &event); + + /* Return buffer to vb queue */ + active_buf->vb.v4l2_buf.sequence = ctx->fh.sequence; + vb2_buffer_done(&active_buf->vb, VB2_BUF_STATE_DONE); + + /* Release buffer from the device */ + msm_fd_hw_buffer_done(fd, active_buf); +} + +/* + * msm_fd_irq - Fd device irq handler. + * @irq: Pointer to work struct. + * @dev_id: Pointer to fd device. + */ +static irqreturn_t msm_fd_irq(int irq, void *dev_id) +{ + struct msm_fd_device *fd = dev_id; + + if (msm_fd_hw_is_finished(fd)) + queue_work(fd->work_queue, &fd->work); + else + dev_err(fd->dev, "Something wrong! FD still running\n"); + + return IRQ_HANDLED; +} + +/* + * 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; + + /* 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); + 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; + } + + fd->vdd = regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(fd->vdd)) { + dev_err(&pdev->dev, "Fail to get vdd regulator\n"); + ret = -ENODEV; + goto error_get_regulator; + } + + ret = msm_fd_hw_get_clocks(fd); + if (ret < 0) { + dev_err(&pdev->dev, "Fail to get clocks\n"); + goto error_get_clocks; + } + + ret = msm_fd_hw_get_iommu(fd); + if (ret < 0) { + dev_err(&pdev->dev, "Fail to get iommu\n"); + goto error_iommu_get; + } + + fd->irq_num = platform_get_irq(pdev, 0); + if (fd->irq_num < 0) { + dev_err(&pdev->dev, "Can not get fd irq resource\n"); + ret = -ENODEV; + goto error_irq_request; + } + + ret = devm_request_irq(&pdev->dev, fd->irq_num, msm_fd_irq, + IRQF_TRIGGER_RISING, dev_name(&pdev->dev), fd); + if (ret) { + dev_err(&pdev->dev, "Can not claim IRQ %d\n", fd->irq_num); + goto error_irq_request; + } + + fd->work_queue = alloc_workqueue(MSM_FD_DRV_NAME, + WQ_HIGHPRI | WQ_NON_REENTRANT | WQ_UNBOUND, 0); + if (!fd->work_queue) { + dev_err(&pdev->dev, "Can not register workqueue\n"); + ret = -ENOMEM; + goto error_alloc_workqueue; + } + INIT_WORK(&fd->work, msm_fd_wq_handler); + INIT_LIST_HEAD(&fd->buf_queue); + + /* 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: + destroy_workqueue(fd->work_queue); +error_alloc_workqueue: + devm_free_irq(&pdev->dev, fd->irq_num, fd); +error_irq_request: + msm_fd_hw_put_iommu(fd); +error_iommu_get: + msm_fd_hw_put_clocks(fd); +error_get_clocks: + regulator_put(fd->vdd); +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 (NULL == fd) { + dev_err(&pdev->dev, "Can not get fd drvdata\n"); + return 0; + } + video_unregister_device(&fd->video); + destroy_workqueue(fd->work_queue); + v4l2_device_unregister(&fd->v4l2_dev); + devm_free_irq(&pdev->dev, fd->irq_num, fd); + msm_fd_hw_put_iommu(fd); + msm_fd_hw_put_clocks(fd); + regulator_put(fd->vdd); + 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"); diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.h b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.h new file mode 100644 index 000000000000..b4a7fc453f0c --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_dev.h @@ -0,0 +1,238 @@ +/* Copyright (c) 2014, 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. + */ + +#ifndef __MSM_FD_DEV_H__ +#define __MSM_FD_DEV_H__ + +#include +#include +#include +#include + +/* Maximum number of result buffers */ +#define MSM_FD_MAX_RESULT_BUFS 5 +/* Max number of clocks defined in device tree */ +#define MSM_FD_MAX_CLK_NUM 10 +/* Max number of clock rates defined in device tree */ +#define MSM_FD_MAX_CLK_RATES 5 +/* Max number of faces which can be detected in one hw processing */ +#define MSM_FD_MAX_FACES_DETECTED 32 + +/* + * struct msm_fd_size - Structure contain FD size related values. + * @width: Image width. + * @height: Image height. + * @reg_val: Register value for this size. + * @work_size: Working buffer size in bytes for this size. + */ +struct msm_fd_size { + int width; + int height; + u32 reg_val; + int work_size; +}; + +/* + * struct msm_fd_setings - Structure contain FD settings values. + * @min_size_index: Minimum face size array index. + * @angle_index: Face detection angle array index. + * @direction_index: Face detection direction array index. + * @threshold: Face detection threshold value. + * @speed: Face detection speed value (it should match with clock rate index). + */ +struct msm_fd_setings { + unsigned int min_size_index; + unsigned int angle_index; + unsigned int direction_index; + unsigned int threshold; + unsigned int speed; +}; + +/* + * struct msm_fd_format - Structure contain FD format settings. + * @size: Pointer to fd size struct used for this format. + * @crop: V4l2 crop structure. + * @bytesperline: Bytes per line of input image buffer. + * @sizeimage: Size of input image buffer. + * @pixelformat: Pixel format of input image buffer. + */ +struct msm_fd_format { + struct msm_fd_size *size; + struct v4l2_rect crop; + int bytesperline; + int sizeimage; + u32 pixelformat; +}; + +/* + * struct msm_fd_mem_pool - Structure contain FD memory pool information. + * @client: Pointer to ion client. + * @domain_num: Domain number associated with FD hw. + */ +struct msm_fd_mem_pool { + struct ion_client *client; + int domain_num; +}; + +/* + * struct msm_fd_buf_handle - Structure contain FD buffer handle information. + * @fd: ion FD from which this buffer is imported. + * @pool: Pointer to FD memory pool struct. + * @handle: Pointer to ion handle. + * @size: Size of the buffer. + * @addr: Adders of FD mmu mapped buffer. This address should be set to FD hw. + */ +struct msm_fd_buf_handle { + int fd; + struct msm_fd_mem_pool *pool; + void *handle; + unsigned long size; + ion_phys_addr_t addr; +}; + +/* + * struct msm_fd_buffer - Vb2 buffer wrapper structure. + * @vb: Videobuf 2 buffer structure. + * @active: Flag indicating if buffer currently used by FD hw. + * @completion: Completion need to wait on, if buffer is used by FD hw. + * @format: Format information of this buffer. + * @settings: Settings value of this buffer. + * @work_addr: Working buffer address need to be used when for this buffer. + * @list: Buffer is part of FD device processing queue + */ +struct msm_fd_buffer { + struct vb2_buffer vb; + atomic_t active; + struct completion completion; + struct msm_fd_format format; + struct msm_fd_setings settings; + ion_phys_addr_t work_addr; + struct list_head list; +}; + +/* + * struct msm_fd_stats - Structure contains FD result statistic information. + * @frame_id: Frame id for which statistic corresponds to. + * @face_cnt: Number of faces detected and included in face data. + * @face_data: Structure containing detected face data information. + */ +struct msm_fd_stats { + atomic_t frame_id; + u32 face_cnt; + struct msm_fd_face_data face_data[MSM_FD_MAX_FACES_DETECTED]; +}; + +/* + * struct fd_ctx - Structure contains per open file handle context. + * @fd_device: Pointer to fd device. + * @fh: V4l2 file handle. + * @vb2_q: Videobuf 2 queue. + * @sequence: Sequence number for this statistic. + * @format: Current format. + * @settings: Current settings. + * @mem_pool: FD hw memory pool. + * @stats: Pointer to statistic buffers. + * @work_buf: Working memory buffer handle. + * @wait_stop_stream: Pointer to completion to wait on stop stream. + */ +struct fd_ctx { + struct msm_fd_device *fd_device; + struct v4l2_fh fh; + struct vb2_queue vb2_q; + unsigned int sequence; + atomic_t subscribed_for_event; + struct msm_fd_format format; + struct msm_fd_setings settings; + struct msm_fd_mem_pool mem_pool; + struct msm_fd_stats *stats; + struct msm_fd_buf_handle work_buf; + struct completion *wait_stop_stream; +}; + +/* + * enum msm_fd_device_state - FD device state. + * @MSM_FD_DEVICE_IDLE: Device is idle, we can start with processing. + * @MSM_FD_DEVICE_RUNNING: Device is running, next processing will be + * scheduled from fd irq. + */ +enum msm_fd_device_state { + MSM_FD_DEVICE_IDLE, + MSM_FD_DEVICE_RUNNING, +}; + +/* + * enum msm_fd_mem_resources - FD device iomem resources. + * @MSM_FD_IOMEM_CORE: Index of fd core registers. + * @MSM_FD_IOMEM_MISC: Index of fd misc registers. + * @MSM_FD_IOMEM_VBIF: Index of fd vbif registers. + * @MSM_FD_IOMEM_LAST: Not valid. + */ +enum msm_fd_mem_resources { + MSM_FD_IOMEM_CORE, + MSM_FD_IOMEM_MISC, + MSM_FD_IOMEM_VBIF, + MSM_FD_IOMEM_LAST +}; + +/* + * struct msm_fd_device - FD device structure. + * @lock: Lock used for reference count. + * @slock: Spinlock used to protect FD device struct. + * @ref_count: Device reference count. + * @res_mem: Array of memory resources used by FD device. + * @iomem_base: Array of register mappings used by FD device. + * @vdd: Pointer to vdd regulator. + * @clk_num: Number of clocks attached to the device. + * @clk: Array of clock resources used by fd device. + * @clk_rates: Array of clock rates set. + * @iommu_domain: Pointer to FD device iommu domain handler. + * @iommu_domain_num: FD device iommu domain number. + * @iommu_dev: Pointer to Ion iommu device. + * @dev: Pointer to device struct. + * @v4l2_dev: V4l2 device. + * @video: Video device. + * @state: FD device state. + * @buf_queue: FD device processing queue. + * @work_queue: Pointer to FD device IRQ bottom half workqueue. + * @work: IRQ bottom half work struct. + */ +struct msm_fd_device { + struct mutex lock; + spinlock_t slock; + int ref_count; + + int irq_num; + struct resource *res_mem[MSM_FD_IOMEM_LAST]; + void __iomem *iomem_base[MSM_FD_IOMEM_LAST]; + struct resource *ioarea[MSM_FD_IOMEM_LAST]; + struct regulator *vdd; + + unsigned int clk_num; + struct clk *clk[MSM_FD_MAX_CLK_NUM]; + unsigned int clk_rates_num; + unsigned int clk_rates[MSM_FD_MAX_CLK_RATES][MSM_FD_MAX_CLK_NUM]; + + struct iommu_domain *iommu_domain; + int iommu_domain_num; + + struct device *iommu_dev; + struct device *dev; + struct v4l2_device v4l2_dev; + struct video_device video; + + enum msm_fd_device_state state; + struct list_head buf_queue; + struct workqueue_struct *work_queue; + struct work_struct work; +}; + +#endif /* __MSM_FD_DEV_H__ */ diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.c b/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.c new file mode 100644 index 000000000000..a0dd3957082b --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.c @@ -0,0 +1,1000 @@ +/* Copyright (c) 2014, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fd_dev.h" +#include "msm_fd_regs.h" + +/* Fd iommu partition definition */ +static struct msm_iova_partition msm_fd_fw_partition = { + .start = SZ_128K, + .size = SZ_2G - SZ_128K, +}; + +/* Fd iommu domain definition */ +static struct msm_iova_layout msm_fd_fw_layout = { + .partitions = &msm_fd_fw_partition, + .npartitions = 1, + .client_name = "fd_iommu", + .domain_flags = 0, +}; + +/* + * msm_fd_hw_read_reg - Fd read from register. + * @fd: Pointer to fd device. + * @base_idx: Fd memory resource index. + * @reg: Register addr need to be read from. + */ +static inline u32 msm_fd_hw_read_reg(struct msm_fd_device *fd, + enum msm_fd_mem_resources base_idx, u32 reg) +{ + return readl_relaxed(fd->iomem_base[base_idx] + reg); +} + +/* + * msm_fd_hw_read_reg - Fd write to register. + * @fd: Pointer to fd device. + * @base_idx: Fd memory resource index. + * @reg: Register addr need to be read from. + e @value: Value to be written. + */ +static inline void msm_fd_hw_write_reg(struct msm_fd_device *fd, + enum msm_fd_mem_resources base_idx, u32 reg, u32 value) +{ + writel_relaxed(value, fd->iomem_base[base_idx] + reg); +} + +/* + * msm_fd_hw_reg_clr - Fd clear register bits. + * @fd: Pointer to fd device. + * @base_idx: Fd memory resource index. + * @reg: Register addr need to be read from. + * @clr_bits: Bits need to be clear from register. + */ +static inline void msm_fd_hw_reg_clr(struct msm_fd_device *fd, + enum msm_fd_mem_resources mmio_range, u32 reg, u32 clr_bits) +{ + u32 bits = msm_fd_hw_read_reg(fd, mmio_range, reg); + + msm_fd_hw_write_reg(fd, mmio_range, reg, (bits & ~clr_bits)); +} + +/* + * msm_fd_hw_reg_clr - Fd set register bits. + * @fd: Pointer to fd device. + * @base_idx: Fd memory resource index. + * @reg: Register addr need to be read from. + * @set_bits: Bits need to be set to register. + */ +static inline void msm_fd_hw_reg_set(struct msm_fd_device *fd, + enum msm_fd_mem_resources mmio_range, u32 reg, u32 set_bits) +{ + u32 bits = msm_fd_hw_read_reg(fd, mmio_range, reg); + + msm_fd_hw_write_reg(fd, mmio_range, reg, (bits | set_bits)); +} + +/* + * msm_fd_hw_reg_clr - Fd set size mode register. + * @fd: Pointer to fd device. + * @mode: Size mode to be set. + */ +static inline void msm_fd_hw_set_size_mode(struct msm_fd_device *fd, u32 mode) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_IMAGE_SIZE, mode); +} + +/* + * msm_fd_hw_reg_clr - Fd set crop registers. + * @fd: Pointer to fd device. + * @crop: Pointer to v4l2 crop struct containing the crop information + */ +static inline void msm_fd_hw_set_crop(struct msm_fd_device *fd, + struct v4l2_rect *crop) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_START_X, + (crop->top & MSM_FD_START_X_MASK)); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_START_Y, + (crop->left & MSM_FD_START_Y_MASK)); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_SIZE_X, + (crop->width & MSM_FD_SIZE_X_MASK)); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_SIZE_Y, + (crop->height & MSM_FD_SIZE_Y_MASK)); +} + +/* + * msm_fd_hw_reg_clr - Fd set bytes per line register. + * @fd: Pointer to fd device. + * @b: Bytes per line need to be set. + */ +static inline void msm_fd_hw_set_bytesperline(struct msm_fd_device *fd, u32 b) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_LINE_BYTES, + (b & MSM_FD_LINE_BYTES_MASK)); +} + +/* + * msm_fd_hw_reg_clr - Fd set image address. + * @fd: Pointer to fd device. + * @addr: Input image address need to be set. + */ +static inline void msm_fd_hw_set_image_addr(struct msm_fd_device *fd, u32 addr) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_IMAGE_ADDR, addr); +} + +/* + * msm_fd_hw_set_work_addr - Fd set working buffer address. + * @fd: Pointer to fd device. + * @addr: Working buffer address need to be set. + */ +static inline void msm_fd_hw_set_work_addr(struct msm_fd_device *fd, u32 addr) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_WORK_ADDR, addr); +} + +/* + * msm_fd_hw_set_direction_angle - Fd set face direction and face angle. + * @fd: Pointer to fd device. + * @direction: Face direction need to be set. + * @angle: Face angle need to be set. + */ +static inline void msm_fd_hw_set_direction_angle(struct msm_fd_device *fd, + u32 direction, u32 angle) +{ + u32 reg; + + reg = direction | (angle ? 1 << (angle + 1) : 0); + if (reg > MSM_FD_CONDT_DIR_MAX) + reg = MSM_FD_CONDT_DIR_MAX; + + msm_fd_hw_reg_set(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONDT, + (reg << MSM_FD_CONDT_DIR_SHIFT)); +} + +/* + * msm_fd_hw_set_min_face - Fd set minimum face size register. + * @fd: Pointer to fd device. + * @size: Minimum face size need to be set. + */ +static inline void msm_fd_hw_set_min_face(struct msm_fd_device *fd, u32 size) +{ + msm_fd_hw_reg_set(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONDT, + (size & MSM_FD_CONDT_MIN_MASK) << MSM_FD_CONDT_MIN_SHIFT); +} + +/* + * msm_fd_hw_set_threshold - Fd set detection threshold register. + * @fd: Pointer to fd device. + * @c: Maximum face count need to be set. + */ +static inline void msm_fd_hw_set_threshold(struct msm_fd_device *fd, u32 thr) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_DHINT, + (thr & MSM_FD_DHINT_MASK)); +} + +/* + * msm_fd_hw_srst - Sw reset control registers. + * @fd: Pointer to fd device. + * + * Before every processing we need to toggle this bit, + * This functions set sw reset control bit to 1/0. + */ +static inline void msm_fd_hw_srst(struct msm_fd_device *fd) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONTROL, + MSM_FD_CONTROL_SRST); + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONTROL, 0); +} + +/* + * msm_fd_hw_get_face_count - Fd read face count register. + * @fd: Pointer to fd device. + */ +int msm_fd_hw_get_face_count(struct msm_fd_device *fd) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_RESULT_CNT); + + return reg & MSM_FD_RESULT_CNT_MASK; +} + +/* + * msm_fd_hw_run - Starts face detection engine. + * @fd: Pointer to fd device. + * + * Before call this function make sure that control sw reset is perfomed + * (see function msm_fd_hw_srst). + * NOTE: Engine need to be reset before started again. + */ +static inline void msm_fd_hw_run(struct msm_fd_device *fd) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONTROL, + MSM_FD_CONTROL_RUN); +} + +/* + * msm_fd_hw_is_finished - Check if fd hw engine is done with processing. + * @fd: Pointer to fd device. + * + * NOTE: If finish bit is not set, we should not read the result. + */ +int msm_fd_hw_is_finished(struct msm_fd_device *fd) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONTROL); + + return reg & MSM_FD_CONTROL_FINISH; +} + +/* + * msm_fd_hw_is_runnig - Check if fd hw engine is busy. + * @fd: Pointer to fd device. + */ +int msm_fd_hw_is_runnig(struct msm_fd_device *fd) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, MSM_FD_CONTROL); + + return reg & MSM_FD_CONTROL_RUN; +} + +/* + * msm_fd_hw_get_result_x - Get fd result center x coordinate. + * @fd: Pointer to fd device. + * @idx: Result face index + */ +int msm_fd_hw_get_result_x(struct msm_fd_device *fd, int idx) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, + MSM_FD_RESULT_CENTER_X(idx)); + + return reg; +} + +/* + * msm_fd_hw_get_result_y - Get fd result center y coordinate. + * @fd: Pointer to fd device. + * @idx: Result face index + */ +int msm_fd_hw_get_result_y(struct msm_fd_device *fd, int idx) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, + MSM_FD_RESULT_CENTER_Y(idx)); + + return reg; +} + +/* + * msm_fd_hw_get_result_conf_size - Get fd result confident level and size. + * @fd: Pointer to fd device. + * @idx: Result face index. + * @conf: Pointer to confident value need to be filled. + * @size: Pointer to size value need to be filled. + */ +void msm_fd_hw_get_result_conf_size(struct msm_fd_device *fd, + int idx, u32 *conf, u32 *size) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, + MSM_FD_RESULT_CONF_SIZE(idx)); + + *conf = (reg >> MSM_FD_RESULT_CONF_SHIFT) & MSM_FD_RESULT_CONF_MASK; + *size = (reg >> MSM_FD_RESULT_SIZE_SHIFT) & MSM_FD_RESULT_SIZE_MASK; +} + +/* + * msm_fd_hw_get_result_angle_pose - Get fd result angle and pose. + * @fd: Pointer to fd device. + * @idx: Result face index. + * @angle: Pointer to angle value need to be filled. + * @pose: Pointer to pose value need to be filled. + */ +void msm_fd_hw_get_result_angle_pose(struct msm_fd_device *fd, int idx, + u32 *angle, u32 *pose) +{ + u32 reg; + + reg = msm_fd_hw_read_reg(fd, MSM_FD_IOMEM_CORE, + MSM_FD_RESULT_CONF_SIZE(idx)); + + *angle = (reg >> MSM_FD_RESULT_ANGLE_SHIFT) & MSM_FD_RESULT_ANGLE_MASK; + *pose = (reg >> MSM_FD_RESULT_POSE_SHIFT) & MSM_FD_RESULT_POSE_MASK; +} + +/* + * msm_fd_hw_vbif_register - Configure and enable vbif interface. + * @fd: Pointer to fd device. + */ +void msm_fd_hw_vbif_register(struct msm_fd_device *fd) +{ + + msm_fd_hw_reg_set(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_CLKON, 0x1); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_IN_RD_LIM_CONF0, 0x10); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_IN_WR_LIM_CONF0, 0x10); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_OUT_RD_LIM_CONF0, 0x10); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_OUT_WR_LIM_CONF0, 0x10); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_DDR_OUT_MAX_BURST, 0x0F); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_ARB_CTL, 0x30); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_OUT_AXI_AMEMTYPE_CONF0, 0x02); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_OUT_AXI_AOOO_EN, 0x10001); + + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_ROUND_ROBIN_QOS_ARB, 0x03); +} + +/* + * msm_fd_hw_vbif_unregister - Disable vbif interface. + * @fd: Pointer to fd device. + */ +void msm_fd_hw_vbif_unregister(struct msm_fd_device *fd) +{ + msm_fd_hw_write_reg(fd, MSM_FD_IOMEM_VBIF, + MSM_FD_VBIF_CLKON, 0x0); +} + +/* + * msm_fd_hw_release_mem_resources - Releases memory resources. + * @fd: Pointer to fd device. + */ +void msm_fd_hw_release_mem_resources(struct msm_fd_device *fd) +{ + int i; + + /* Prepare memory resources */ + for (i = 0; i < MSM_FD_IOMEM_LAST; i++) { + if (fd->iomem_base[i]) { + iounmap(fd->iomem_base[i]); + fd->iomem_base[i] = NULL; + } + if (fd->ioarea[i]) { + release_mem_region(fd->res_mem[i]->start, + resource_size(fd->res_mem[i])); + fd->ioarea[i] = NULL; + } + fd->res_mem[i] = NULL; + } +} + +/* + * msm_fd_hw_get_mem_resources - Get memory resources. + * @pdev: Pointer to fd platform device. + * @fd: Pointer to fd device. + * + * Get and ioremap platform memory resources. + */ +int msm_fd_hw_get_mem_resources(struct platform_device *pdev, + struct msm_fd_device *fd) +{ + int i; + int ret = 0; + + /* Prepare memory resources */ + for (i = 0; i < MSM_FD_IOMEM_LAST; i++) { + /* Get resources */ + fd->res_mem[i] = platform_get_resource(pdev, + IORESOURCE_MEM, i); + if (!fd->res_mem[i]) { + dev_err(fd->dev, "Fail get resource idx %d\n", + i); + ret = -ENODEV; + break; + } + + fd->ioarea[i] = request_mem_region(fd->res_mem[i]->start, + resource_size(fd->res_mem[i]), fd->res_mem[i]->name); + if (!fd->ioarea[i]) { + dev_err(fd->dev, "%s can not request mem\n", + fd->res_mem[i]->name); + ret = -ENODEV; + break; + } + + fd->iomem_base[i] = ioremap(fd->res_mem[i]->start, + resource_size(fd->res_mem[i])); + if (!fd->iomem_base[i]) { + dev_err(fd->dev, "%s can not remap region\n", + fd->res_mem[i]->name); + ret = -ENODEV; + break; + } + } + + if (ret < 0) + msm_fd_hw_release_mem_resources(fd); + + return ret; +} + +/* + * msm_fd_hw_get_iommu - Get fd iommu. + * @fd: Pointer to fd device. + * + * Registers and get fd iommu domain. + */ +int msm_fd_hw_get_iommu(struct msm_fd_device *fd) +{ + int ret; + + fd->iommu_domain_num = msm_register_domain(&msm_fd_fw_layout); + if (fd->iommu_domain_num < 0) { + dev_err(fd->dev, "Can not register iommu domain\n"); + ret = -ENODEV; + goto error; + } + + fd->iommu_domain = msm_get_iommu_domain(fd->iommu_domain_num); + if (!fd->iommu_domain) { + dev_err(fd->dev, "Can not get iommu domain\n"); + ret = -ENODEV; + goto error; + } + + fd->iommu_dev = msm_iommu_get_ctx("camera_fd"); + if (IS_ERR(fd->iommu_dev)) { + dev_err(fd->dev, "Can not get iommu device\n"); + ret = -EPROBE_DEFER; + goto error_iommu_get_dev; + } + + return 0; + +error_iommu_get_dev: + msm_unregister_domain(fd->iommu_domain); + fd->iommu_domain = NULL; + fd->iommu_domain_num = -1; +error: + return ret; +} + +/* + * msm_fd_hw_get_iommu - Put fd iommu. + * @fd: Pointer to fd device. + * + * Unregisters fd iommu domain. + */ +void msm_fd_hw_put_iommu(struct msm_fd_device *fd) +{ + msm_unregister_domain(fd->iommu_domain); + fd->iommu_domain = NULL; +} + +/* + * msm_fd_hw_get_clocks - Get fd clocks. + * @fd: Pointer to fd device. + * + * Read clock information from device tree and perform get clock. + */ +int msm_fd_hw_get_clocks(struct msm_fd_device *fd) +{ + const char *clk_name; + size_t cnt; + int i; + int ret; + + cnt = of_property_count_strings(fd->dev->of_node, "clock-names"); + if (cnt > MSM_FD_MAX_CLK_NUM) { + dev_err(fd->dev, "Exceed max number of clocks %zu\n", cnt); + return -EINVAL; + } + + for (i = 0; i < cnt; i++) { + ret = of_property_read_string_index(fd->dev->of_node, + "clock-names", i, &clk_name); + if (ret < 0) { + dev_err(fd->dev, "Can not read clock name %d\n", i); + goto error; + } + + fd->clk[i] = clk_get(fd->dev, clk_name); + if (IS_ERR(fd->clk[i])) { + ret = -ENOENT; + dev_err(fd->dev, "Error clock get %s\n", clk_name); + goto error; + } + dev_dbg(fd->dev, "Clock name idx %d %s\n", i, clk_name); + } + fd->clk_num = cnt; + + ret = of_property_read_u32_array(fd->dev->of_node, "clock-rates", + fd->clk_rates[0], fd->clk_num); + if (ret < 0) { + dev_err(fd->dev, "Can not get clock rates\n"); + goto error; + } + fd->clk_rates_num = 1; + + return 0; +error: + for (; i > 0; i--) + clk_put(fd->clk[i - 1]); + + return ret; +} + +/* + * msm_fd_hw_get_clocks - Put fd clocks. + * @fd: Pointer to fd device. + */ +int msm_fd_hw_put_clocks(struct msm_fd_device *fd) +{ + int i; + + for (i = 0; i < fd->clk_num; i++) { + if (!IS_ERR_OR_NULL(fd->clk[i])) + clk_put(fd->clk[i]); + fd->clk_num = 0; + } + return 0; +} + +/* + * msm_fd_hw_set_clock_rate_idx - Set clock rate based on the index. + * @fd: Pointer to fd device. + * @idx: Clock Array index described in device tree. + */ +static int msm_fd_hw_set_clock_rate_idx(struct msm_fd_device *fd, + unsigned int idx) +{ + int ret; + long clk_rate; + int i; + + if (idx >= fd->clk_rates_num) + idx = fd->clk_rates_num - 1; + + for (i = 0; i < fd->clk_num; i++) { + + clk_rate = clk_round_rate(fd->clk[i], fd->clk_rates[idx][i]); + if (clk_rate < 0) { + dev_err(fd->dev, "Fail clock round rate\n"); + return -EINVAL; + } + + ret = clk_set_rate(fd->clk[i], clk_rate); + if (ret < 0) { + dev_err(fd->dev, "Fail clock rate %ld\n", clk_rate); + return -EINVAL; + } + dev_dbg(fd->dev, "Clk rate %d-%ld idx %d\n", i, clk_rate, idx); + } + + return 0; +} +/* + * msm_fd_hw_enable_clocks - Prepare and enable fd clocks. + * @fd: Pointer to fd device. + */ +static int msm_fd_hw_enable_clocks(struct msm_fd_device *fd) +{ + int i; + int ret; + + for (i = 0; i < fd->clk_num; i++) { + ret = clk_prepare(fd->clk[i]); + if (ret < 0) { + dev_err(fd->dev, "clock prepare failed %d\n", i); + goto error; + } + + ret = clk_enable(fd->clk[i]); + if (ret < 0) { + dev_err(fd->dev, "clock enable %d\n", i); + clk_unprepare(fd->clk[i]); + goto error; + } + } + + return 0; +error: + for (; i > 0; i--) { + clk_disable(fd->clk[i - 1]); + clk_unprepare(fd->clk[i - 1]); + } + return ret; +} +/* + * msm_fd_hw_disable_clocks - Disable fd clock. + * @fd: Pointer to fd device. + */ +static void msm_fd_hw_disable_clocks(struct msm_fd_device *fd) +{ + int i; + + for (i = 0; i < fd->clk_num; i++) { + clk_disable(fd->clk[i]); + clk_unprepare(fd->clk[i]); + } +} + +/* + * msm_fd_hw_get - Get fd hw for performing any hw operation. + * @fd: Pointer to fd device. + * @clock_rate_idx: Clock rate index. + * + * Prepare fd hw for operation. Have reference count protected by + * fd device mutex. + */ +int msm_fd_hw_get(struct msm_fd_device *fd, unsigned int clock_rate_idx) +{ + int ret; + + mutex_lock(&fd->lock); + + if (fd->ref_count == 0) { + ret = msm_fd_hw_set_clock_rate_idx(fd, clock_rate_idx); + if (ret < 0) { + dev_err(fd->dev, "Fail to enable vdd\n"); + goto error; + } + + ret = regulator_enable(fd->vdd); + if (ret < 0) { + dev_err(fd->dev, "Fail to enable vdd\n"); + goto error; + } + + ret = msm_fd_hw_enable_clocks(fd); + if (ret < 0) { + dev_err(fd->dev, "Fail to enable clocks\n"); + goto error_clocks; + } + msm_fd_hw_vbif_register(fd); + } + + fd->ref_count++; + mutex_unlock(&fd->lock); + + return 0; + +error_clocks: + regulator_disable(fd->vdd); +error: + mutex_unlock(&fd->lock); + return ret; +} + +/* + * msm_fd_hw_get - Put fd hw. + * @fd: Pointer to fd device. + * + * Release fd hw. Have reference count protected by + * fd device mutex. + */ +void msm_fd_hw_put(struct msm_fd_device *fd) +{ + mutex_lock(&fd->lock); + BUG_ON(fd->ref_count == 0); + + if (--fd->ref_count == 0) { + msm_fd_hw_vbif_unregister(fd); + msm_fd_hw_disable_clocks(fd); + regulator_disable(fd->vdd); + } + mutex_unlock(&fd->lock); +} + +/* + * msm_fd_hw_map_buffer - Map buffer to fd hw mmu. + * @pool: Pointer to fd memory pool. + * @fd: Ion fd. + * @buf: Fd buffer handle, for storing mapped buffer information. + * + * It will map ion fd to fd hw mmu. + */ +int msm_fd_hw_map_buffer(struct msm_fd_mem_pool *pool, int fd, + struct msm_fd_buf_handle *buf) +{ + int ret; + + if (!pool || fd < 0) + return -EINVAL; + + buf->pool = pool; + buf->fd = fd; + + buf->handle = ion_import_dma_buf(pool->client, buf->fd); + if (IS_ERR_OR_NULL(buf->handle)) + goto error; + + ret = ion_map_iommu(pool->client, buf->handle, pool->domain_num, + 0, SZ_4K, 0, &buf->addr, &buf->size, 0, 0); + if (ret < 0) + goto error_map_iommu; + + return buf->size; + +error_map_iommu: + ion_free(pool->client, buf->handle); +error: + return -ENOMEM; +} + +/* + * msm_fd_hw_unmap_buffer - Unmap buffer from fd hw mmu. + * @buf: Fd buffer handle, for storing mapped buffer information. + */ +void msm_fd_hw_unmap_buffer(struct msm_fd_buf_handle *buf) +{ + if (buf->size) + ion_unmap_iommu(buf->pool->client, buf->handle, + buf->pool->domain_num, 0); + + if (!IS_ERR_OR_NULL(buf->handle)) + ion_free(buf->pool->client, buf->handle); + + buf->fd = -1; + buf->pool = NULL; +} + +/* + * msm_fd_hw_enable - Configure and enable fd hw. + * @fd: Fd device. + * @buffer: Buffer need to be processed. + * + * Configure and starts fd processing with given buffer. + * NOTE: Fd will not be enabled if engine is in running state. + */ +static int msm_fd_hw_enable(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer) +{ + struct msm_fd_buf_handle *buf_handle = + buffer->vb.planes[0].mem_priv; + + if (msm_fd_hw_is_runnig(fd)) { + dev_err(fd->dev, "Device is busy we can not enable\n"); + return 0; + } + + msm_fd_hw_srst(fd); + msm_fd_hw_set_size_mode(fd, buffer->format.size->reg_val); + msm_fd_hw_set_crop(fd, &buffer->format.crop); + msm_fd_hw_set_bytesperline(fd, buffer->format.bytesperline); + msm_fd_hw_set_image_addr(fd, buf_handle->addr); + msm_fd_hw_set_work_addr(fd, buffer->work_addr); + msm_fd_hw_set_min_face(fd, buffer->settings.min_size_index); + msm_fd_hw_set_threshold(fd, buffer->settings.threshold); + msm_fd_hw_set_direction_angle(fd, buffer->settings.direction_index, + buffer->settings.angle_index); + msm_fd_hw_run(fd); + return 1; +} + +/* + * msm_fd_hw_try_enable - Try to enable fd hw. + * @fd: Fd device. + * @buffer: Buffer need to be processed. + * @state: Enable on device state + * + * It will enable fd hw if actual device state is equal with state argument. + */ +static int msm_fd_hw_try_enable(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer, enum msm_fd_device_state state) +{ + int enabled = 0; + + if (state == fd->state) { + + fd->state = MSM_FD_DEVICE_RUNNING; + atomic_set(&buffer->active, 1); + + msm_fd_hw_enable(fd, buffer); + enabled = 1; + } + return enabled; +} + +/* + * msm_fd_hw_next_buffer - Get next buffer from fd device processing queue. + * @fd: Fd device. + */ +static struct msm_fd_buffer *msm_fd_hw_next_buffer(struct msm_fd_device *fd) +{ + struct msm_fd_buffer *buffer = NULL; + + if (!list_empty(&fd->buf_queue)) + buffer = list_first_entry(&fd->buf_queue, + struct msm_fd_buffer, list); + + return buffer; +} + +/* + * msm_fd_hw_add_buffer - Add buffer to fd device processing queue. + * @fd: Fd device. + */ +void msm_fd_hw_add_buffer(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer) +{ + spin_lock(&fd->slock); + + atomic_set(&buffer->active, 0); + init_completion(&buffer->completion); + + INIT_LIST_HEAD(&buffer->list); + list_add_tail(&buffer->list, &fd->buf_queue); + spin_unlock(&fd->slock); +} + +/* + * msm_fd_hw_remove_buffers_from_queue - Removes buffer from + * fd device processing queue. + * @fd: Fd device. + */ +void msm_fd_hw_remove_buffers_from_queue(struct msm_fd_device *fd, + struct vb2_queue *vb2_q) +{ + struct msm_fd_buffer *curr_buff; + struct msm_fd_buffer *temp; + struct msm_fd_buffer *active_buffer; + + spin_lock(&fd->slock); + + active_buffer = NULL; + list_for_each_entry_safe(curr_buff, temp, &fd->buf_queue, list) { + if (curr_buff->vb.vb2_queue == vb2_q) { + + if (atomic_read(&curr_buff->active)) + active_buffer = curr_buff; + else + list_del(&curr_buff->list); + + } + } + spin_unlock(&fd->slock); + + /* We need to wait active buffer to finish */ + if (active_buffer) + wait_for_completion(&active_buffer->completion); + + return; +} + +/* + * msm_fd_hw_buffer_done - Mark as done and removes from processing queue. + * @fd: Fd device. + * @buffer: Fd buffer. + */ +int msm_fd_hw_buffer_done(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer) +{ + int ret = 0; + + spin_lock(&fd->slock); + + if (atomic_read(&buffer->active)) { + atomic_set(&buffer->active, 0); + complete_all(&buffer->completion); + } else { + dev_err(fd->dev, "Buffer is not active\n"); + ret = -1; + } + + spin_unlock(&fd->slock); + + return ret; +} + +/* + * msm_fd_hw_get_active_buffer - Get active buffer from fd processing queue. + * @fd: Fd device. + */ +struct msm_fd_buffer *msm_fd_hw_get_active_buffer(struct msm_fd_device *fd) +{ + struct msm_fd_buffer *buffer = NULL; + + spin_lock(&fd->slock); + if (!list_empty(&fd->buf_queue)) { + buffer = list_first_entry(&fd->buf_queue, + struct msm_fd_buffer, list); + list_del(&buffer->list); + } + spin_unlock(&fd->slock); + + return buffer; +} + +/* + * msm_fd_hw_schedule_and_start - Schedule active buffer and start processing. + * @fd: Fd device. + * + * This can be executed only when device is in idle state. + */ +int msm_fd_hw_schedule_and_start(struct msm_fd_device *fd) +{ + struct msm_fd_buffer *buf; + + spin_lock(&fd->slock); + buf = msm_fd_hw_next_buffer(fd); + if (buf) + msm_fd_hw_try_enable(fd, buf, MSM_FD_DEVICE_IDLE); + + spin_unlock(&fd->slock); + + return 0; +} + +/* + * msm_fd_hw_schedule_next_buffer - Schedule next buffer and start processing. + * @fd: Fd device. + * + * NOTE: This can be executed only when device is in running state. + */ +int msm_fd_hw_schedule_next_buffer(struct msm_fd_device *fd) +{ + struct msm_fd_buffer *buf; + int ret; + + spin_lock(&fd->slock); + + /* We can schedule next buffer only in running state */ + if (fd->state != MSM_FD_DEVICE_RUNNING) { + dev_err(fd->dev, "Can not schedule next buffer\n"); + spin_unlock(&fd->slock); + return -EBUSY; + } + + buf = msm_fd_hw_next_buffer(fd); + if (buf) { + ret = msm_fd_hw_try_enable(fd, buf, MSM_FD_DEVICE_RUNNING); + if (0 == ret) { + dev_err(fd->dev, "Ouch can not process next buffer\n"); + spin_unlock(&fd->slock); + return -EBUSY; + } + } else { + fd->state = MSM_FD_DEVICE_IDLE; + } + spin_unlock(&fd->slock); + + return 0; +} diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.h b/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.h new file mode 100644 index 000000000000..1483d9f74a41 --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_hw.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2014, 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. + */ + +#ifndef __MSM_FD_HW_H__ +#define __MSM_FD_HW_H__ + +#include "msm_fd_dev.h" + +int msm_fd_hw_is_finished(struct msm_fd_device *fd); + +int msm_fd_hw_get_face_count(struct msm_fd_device *fd); + +int msm_fd_hw_get_result_x(struct msm_fd_device *fd, int idx); + +int msm_fd_hw_get_result_y(struct msm_fd_device *fd, int idx); + +void msm_fd_hw_get_result_conf_size(struct msm_fd_device *fd, + int idx, u32 *conf, u32 *size); + +void msm_fd_hw_get_result_angle_pose(struct msm_fd_device *fd, int idx, + u32 *angle, u32 *pose); + +void msm_fd_hw_release_mem_resources(struct msm_fd_device *fd); + +int msm_fd_hw_get_mem_resources(struct platform_device *pdev, + struct msm_fd_device *fd); + +int msm_fd_hw_get_iommu(struct msm_fd_device *fd); + +void msm_fd_hw_put_iommu(struct msm_fd_device *fd); + +int msm_fd_hw_get_clocks(struct msm_fd_device *fd); + +int msm_fd_hw_put_clocks(struct msm_fd_device *fd); + +int msm_fd_hw_get(struct msm_fd_device *fd, unsigned int clock_rate_idx); + +void msm_fd_hw_put(struct msm_fd_device *fd); + +int msm_fd_hw_map_buffer(struct msm_fd_mem_pool *pool, int fd, + struct msm_fd_buf_handle *buf); + +void msm_fd_hw_unmap_buffer(struct msm_fd_buf_handle *buf); + +void msm_fd_hw_add_buffer(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer); + +void msm_fd_hw_remove_buffers_from_queue(struct msm_fd_device *fd, + struct vb2_queue *vb2_q); + +int msm_fd_hw_buffer_done(struct msm_fd_device *fd, + struct msm_fd_buffer *buffer); + +struct msm_fd_buffer *msm_fd_hw_get_active_buffer(struct msm_fd_device *fd); + +int msm_fd_hw_schedule_and_start(struct msm_fd_device *fd); + +int msm_fd_hw_schedule_next_buffer(struct msm_fd_device *fd); + +#endif /* __MSM_FD_HW_H__ */ diff --git a/drivers/media/platform/msm/camera_v2/fd/msm_fd_regs.h b/drivers/media/platform/msm/camera_v2/fd/msm_fd_regs.h new file mode 100644 index 000000000000..c3702301fc20 --- /dev/null +++ b/drivers/media/platform/msm/camera_v2/fd/msm_fd_regs.h @@ -0,0 +1,148 @@ +/* Copyright (c) 2014, 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. + */ + +#ifndef __MSM_FD_REGS_H__ +#define __MSM_FD_REGS_H__ + +/* FD core registers */ +#define MSM_FD_VERSION (0x38) + +#define MSM_FD_CONTROL (0x00) +#define MSM_FD_CONTROL_SRST (1 << 0) +#define MSM_FD_CONTROL_RUN (1 << 1) +#define MSM_FD_CONTROL_FINISH (1 << 2) + +#define MSM_FD_RESULT_CNT (0x04) +#define MSM_FD_RESULT_CNT_MASK (0x3F) + +#define MSM_FD_CONDT (0x08) +#define MSM_FD_CONDT_MIN_MASK (0x03) +#define MSM_FD_CONDT_MIN_SHIFT (0x00) +#define MSM_FD_CONDT_DIR_MAX (0x08) +#define MSM_FD_CONDT_DIR_SHIFT (0x02) + +#define MSM_FD_START_X (0x0C) +#define MSM_FD_START_X_MASK (0x3FF) + +#define MSM_FD_START_Y (0x10) +#define MSM_FD_START_Y_MASK (0x1FF) + +#define MSM_FD_SIZE_X (0x14) +#define MSM_FD_SIZE_X_MASK (0x3FF) + +#define MSM_FD_SIZE_Y (0x18) +#define MSM_FD_SIZE_Y_MASK (0x1FF) + +#define MSM_FD_DHINT (0x1C) +#define MSM_FD_DHINT_MASK (0xF) + +#define MSM_FD_IMAGE_ADDR (0x24) +#define MSM_FD_IMAGE_ADDR_ALIGN (0x8) + +#define MSM_FD_WORK_ADDR (0x28) +#define MSM_FD_WORK_ADDR_ALIGN (0x8) + +#define MSM_FD_IMAGE_SIZE (0x2C) +#define MSM_FD_IMAGE_SIZE_QVGA (0x0) +#define MSM_FD_IMAGE_SIZE_VGA (0x1) +#define MSM_FD_IMAGE_SIZE_WQVGA (0x2) +#define MSM_FD_IMAGE_SIZE_WVGA (0x3) + +#define MSM_FD_LINE_BYTES (0x30) +#define MSM_FD_LINE_BYTES_MASK (0x1FFF) +#define MSM_FD_LINE_BYTES_ALIGN (0x8) + +#define MSM_FD_RESULT_CENTER_X(x) (0x400 + (0x10 * (x))) + +#define MSM_FD_RESULT_CENTER_Y(x) (0x404 + (0x10 * (x))) + +#define MSM_FD_RESULT_CONF_SIZE(x) (0x408 + (0x10 * (x))) +#define MSM_FD_RESULT_SIZE_MASK (0x1FF) +#define MSM_FD_RESULT_SIZE_SHIFT (0x000) +#define MSM_FD_RESULT_CONF_MASK (0xF) +#define MSM_FD_RESULT_CONF_SHIFT (0x9) + +#define MSM_FD_RESULT_ANGLE_POSE(x) (0x40C + (0x10 * (x))) +#define MSM_FD_RESULT_ANGLE_MASK (0x1FF) +#define MSM_FD_RESULT_ANGLE_SHIFT (0x000) +#define MSM_FD_RESULT_POSE_MASK (0x7) +#define MSM_FD_RESULT_POSE_SHIFT (0x9) + +/* FD misc registers */ +#define MSM_FD_MISC_HW_VERSION (0x00) + +#define MSM_FD_MISC_SW_RESET (0x10) +#define MSM_FD_MISC_SW_RESET_SET (1 << 0) + +#define MSM_FD_MISC_FIFO_STATUS (0x14) +#define MSM_FD_MISC_FIFO_STATUS_RFIFO_DCNT_MAST (0x1F) +#define MSM_FD_MISC_FIFO_STATUS_RFIFO_DCNT_SHIFT (0) +#define MSM_FD_MISC_FIFO_STATUS_RFIFO_FULL (1 << 13) +#define MSM_FD_MISC_FIFO_STATUS_RFIFO_EMPTY (1 << 14) +#define MSM_FD_MISC_FIFO_STATUS_WFIFO_DCNT_MAST (0x1F) +#define MSM_FD_MISC_FIFO_STATUS_WFIFO_DCNT_SHIFT (16) +#define MSM_FD_MISC_FIFO_STATUS_WFIFO_EMPTY (1 << 29) +#define MSM_FD_MISC_FIFO_STATUS_WFIFO_FULL (1 << 30) + +#define MSM_FD_MISC_DATA_ENDIAN (0x18) +#define MSM_FD_MISC_DATA_ENDIAN_BYTE_SWAP_SET (1 << 0) + +#define MSM_FD_MISC_VBIF_REQ_PRIO (0x20) +#define MSM_FD_MISC_VBIF_REQ_PRIO_MASK (0x3) + +#define MSM_FD_MISC_VBIF_PRIO_LEVEL (0x24) +#define MSM_FD_MISC_VBIF_PRIO_LEVEL_MASK (0x3) + +#define MSM_FD_MISC_VBIF_MMU_PDIRECT (0x28) +#define MSM_FD_MISC_VBIF_MMU_PDIRECT_INCREMENT (1 << 0) + +#define MSM_FD_MISC_VBIF_IRQ_CLR (0x30) +#define MSM_FD_MISC_VBIF_IRQ_CLR_ALL (1 << 0) + +#define MSM_FD_MISC_VBIF_DONE_STATUS (0x34) +#define MSM_FD_MISC_VBIF_DONE_STATUS_WRITE (1 << 0) +#define MSM_FD_MISC_VBIF_DONE_STATUS_READ (1 << 1) + +#define MSM_FD_MISC_TEST_BUS_SEL (0x40) +#define MSM_FD_MISC_TEST_BUS_SEL_TEST_MODE_MASK (0xF) +#define MSM_FD_MISC_TEST_BUS_SEL_TEST_MODE_SHIFT (0) +#define MSM_FD_MISC_TEST_BUS_SEL_7_0_MASK (0x3) +#define MSM_FD_MISC_TEST_BUS_SEL_7_0_SHIFT (16) +#define MSM_FD_MISC_TEST_BUS_SEL_15_8_MASK (0x3) +#define MSM_FD_MISC_TEST_BUS_SEL_15_8_SHIFT (18) +#define MSM_FD_MISC_TEST_BUS_SEL_23_16_MASK (0x3) +#define MSM_FD_MISC_TEST_BUS_SEL_23_16_SHIFT (20) +#define MSM_FD_MISC_TEST_BUS_SEL_31_24_MASK (0x3) +#define MSM_FD_MISC_TEST_BUS_SEL_31_24_SHIFT (22) + +#define MSM_FD_MISC_AHB_TEST_EN (0x44) +#define MSM_FD_MISC_AHB_TEST_EN_MASK (0x3) + +#define MSM_FD_MISC_FD2VBIF_INT_TEST_SEL (0x48) +#define MSM_FD_MISC_FD2VBIF_INT_TEST_MASK (0xF) + +#define MSM_FD_MISC_TEST_BUS (0x4C) + +/* FD vbif registers */ +#define MSM_FD_VBIF_CLKON (0x04) +#define MSM_FD_VBIF_IN_RD_LIM_CONF0 (0xB0) +#define MSM_FD_VBIF_IN_WR_LIM_CONF0 (0xC0) +#define MSM_FD_VBIF_OUT_RD_LIM_CONF0 (0xD0) +#define MSM_FD_VBIF_OUT_WR_LIM_CONF0 (0xD4) +#define MSM_FD_VBIF_DDR_OUT_MAX_BURST (0xD8) +#define MSM_FD_VBIF_ARB_CTL (0xF0) +#define MSM_FD_VBIF_OUT_AXI_AMEMTYPE_CONF0 (0x160) +#define MSM_FD_VBIF_OUT_AXI_AOOO_EN (0x178) +#define MSM_FD_VBIF_OUT_AXI_AOOO (0x17c) +#define MSM_FD_VBIF_ROUND_ROBIN_QOS_ARB (0x124) + +#endif /* __MSM_FD_REGS_H__ */ diff --git a/include/media/msm_fd.h b/include/media/msm_fd.h new file mode 100644 index 000000000000..71ec56765753 --- /dev/null +++ b/include/media/msm_fd.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2014, 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. + */ +#ifndef __MSM_FD__ +#define __MSM_FD__ + +#include + +#endif /* __MSM_FD__ */ diff --git a/include/uapi/media/msm_fd.h b/include/uapi/media/msm_fd.h new file mode 100644 index 000000000000..229ee322ebd2 --- /dev/null +++ b/include/uapi/media/msm_fd.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2014, 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. + */ +#ifndef __UAPI_MSM_FD__ +#define __UAPI_MSM_FD__ + +#include + +/* + * struct msm_fd_event - Structure contain event info. + * @buf_index: Buffer index. + * @frame_id: Frame id. + * @face_cnt: Detected faces. + */ +struct msm_fd_event { + __u32 buf_index; + __u32 frame_id; + __u32 face_cnt; +}; + +/* + * enum msm_fd_pose - Face pose. + */ +enum msm_fd_pose { + MSM_FD_POSE_FRONT, + MSM_FD_POSE_RIGHT_DIAGONAL, + MSM_FD_POSE_RIGHT, + MSM_FD_POSE_LEFT_DIAGONAL, + MSM_FD_POSE_LEFT, +}; + +/* + * struct msm_fd_face_data - Structure contain detected face data. + * @pose: refer to enum msm_fd_pose. + * @angle: Face angle + * @confidence: Face confidence level. + * @reserved: Reserved data for future use. + * @face: Face rectangle. + */ +struct msm_fd_face_data { + __u32 pose; + __u32 angle; + __u32 confidence; + __u32 reserved; + struct v4l2_rect face; +}; + +/* + * struct msm_fd_result - Structure contain detected faces result. + * @frame_id: Frame id of requested result. + * @face_cnt: Number of result faces, driver can modify this value (to smaller) + * @face_data: Pointer to array of face data structures. + * Array size should not be smaller then face_cnt. + */ +struct msm_fd_result { + __u32 frame_id; + __u32 face_cnt; + struct msm_fd_face_data __user *face_data; +}; + +#ifdef CONFIG_COMPAT +/* + * struct msm_fd_result32 - Compat structure contain detected faces result. + * @frame_id: Frame id of requested result. + * @face_cnt: Number of result faces, driver can modify this value (to smaller) + * @face_data: Pointer to array of face data structures. + * Array size should not be smaller then face_cnt. + */ +struct msm_fd_result32 { + __u32 frame_id; + __u32 face_cnt; + compat_uptr_t face_data; +}; + +/* MSM FD compat private ioctl ID */ +#define VIDIOC_MSM_FD_GET_RESULT32 \ + _IOWR('V', BASE_VIDIOC_PRIVATE, struct msm_fd_result32) +#endif + +/* MSM FD private ioctl ID */ +#define VIDIOC_MSM_FD_GET_RESULT \ + _IOWR('V', BASE_VIDIOC_PRIVATE, struct msm_fd_result) + +/* MSM FD event ID */ +#define MSM_EVENT_FD (V4L2_EVENT_PRIVATE_START) + +/* MSM FD control ID's */ +#define V4L2_CID_FD_SPEED (V4L2_CID_PRIVATE_BASE) +#define V4L2_CID_FD_FACE_ANGLE (V4L2_CID_PRIVATE_BASE + 1) +#define V4L2_CID_FD_MIN_FACE_SIZE (V4L2_CID_PRIVATE_BASE + 2) +#define V4L2_CID_FD_FACE_DIRECTION (V4L2_CID_PRIVATE_BASE + 3) +#define V4L2_CID_FD_DETECTION_THRESHOLD (V4L2_CID_PRIVATE_BASE + 4) +#define V4L2_CID_FD_WORK_MEMORY_SIZE (V4L2_CID_PRIVATE_BASE + 5) +#define V4L2_CID_FD_WORK_MEMORY_FD (V4L2_CID_PRIVATE_BASE + 6) + +#endif /* __UAPI_MSM_FD__ */