Hi Tomasz, On Fri, 2019-09-06 at 18:11 +0800, Jerry-ch Chen wrote: > From: Jerry-ch Chen <jerry-ch.chen@xxxxxxxxxxxx> > > This patch adds the driver of Face Detection (FD) unit in > Mediatek camera system, providing face detection function. > > The mtk-isp directory will contain drivers for multiple IP > blocks found in Mediatek ISP system. It will include ISP Pass 1 > driver (CAM), sensor interface driver, DIP driver and face > detection driver. > > Signed-off-by: Jerry-ch Chen <jerry-ch.chen@xxxxxxxxxxxx> > --- > drivers/media/platform/Kconfig | 2 + > drivers/media/platform/Makefile | 2 + > drivers/media/platform/mtk-isp/fd/Kconfig | 19 + > drivers/media/platform/mtk-isp/fd/Makefile | 5 + > drivers/media/platform/mtk-isp/fd/mtk_fd.h | 148 ++ > drivers/media/platform/mtk-isp/fd/mtk_fd_40.c | 1219 +++++++++++++++++ > include/uapi/linux/mtk-fd-v4l2-controls.h | 69 + > include/uapi/linux/v4l2-controls.h | 4 + > 8 files changed, 1468 insertions(+) > create mode 100644 drivers/media/platform/mtk-isp/fd/Kconfig > create mode 100644 drivers/media/platform/mtk-isp/fd/Makefile > create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd.h > create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd_40.c > create mode 100644 include/uapi/linux/mtk-fd-v4l2-controls.h > [snip] > + > +static void mtk_fd_hw_job_finish(struct mtk_fd_dev *fd, > + enum vb2_buffer_state vb_state) > +{ > + struct mtk_fd_ctx *ctx; > + struct vb2_v4l2_buffer *src_vbuf = NULL, *dst_vbuf = NULL; > + > + ctx = v4l2_m2m_get_curr_priv(fd->m2m_dev); > + src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); > + dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); > + > + v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, > + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); > + v4l2_m2m_buf_done(src_vbuf, vb_state); > + v4l2_m2m_buf_done(dst_vbuf, vb_state); > + v4l2_m2m_job_finish(fd->m2m_dev, ctx->fh.m2m_ctx); > +} > + > +static void mtk_fd_ipi_handler(void *data, unsigned int len, void *priv) > +{ > + struct mtk_fd_dev *fd = (struct mtk_fd_dev *)priv; > + struct device *dev = fd->dev; > + struct ipi_message *fd_ack_msg = (struct ipi_message *)data; > + struct fd_ack_param *fd_ack = &fd_ack_msg->fd_ack_param; > + > + dev_dbg(fd->dev, "fd_ipi_ack_id: %d\n", fd_ack_msg->cmd_id); > + switch (fd_ack_msg->cmd_id) { > + case MTK_FD_IPI_CMD_INIT_ACK: > + return; > + case MTK_FD_IPI_CMD_ENQ_ACK: > + if (fd_ack->ret_code) { > + dev_err(dev, "ipi ret: %d, message: %d\n", > + fd_ack->ret_code, fd_ack->ret_msg); > + pm_runtime_put((fd->dev)); > + mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR); > + } > + return; > + case MTK_FD_IPI_CMD_RESET_ACK: > + return; > + } > +} > + > +static int mtk_fd_hw_connect(struct mtk_fd_dev *fd) > +{ > + int ret; > + > + ret = rproc_boot(fd->rproc_handle); > + > + if (ret < 0) { > + /** > + * Return 0 if downloading firmware successfully, > + * otherwise it is failed > + */ > + dev_err(fd->dev, "Failed to boot rproc\n"); > + return ret; > + } > + > + ret = scp_ipi_register(fd->scp_pdev, SCP_IPI_FD_CMD, > + mtk_fd_ipi_handler, fd); > + if (ret) { > + dev_err(fd->dev, "Failed to register IPI cmd handler\n"); > + goto err_rproc_shutdown; > + } > + > + fd->fd_stream_count++; > + if (fd->fd_stream_count == 1) { > + if (mtk_fd_hw_enable(fd)) { > + ret = -EINVAL; > + goto err_rproc_shutdown; > + } > + } > + return 0; > + > +err_rproc_shutdown: > + rproc_shutdown(fd->rproc_handle); > + return ret; > +} > + > +static void mtk_fd_hw_disconnect(struct mtk_fd_dev *fd) > +{ > + fd->fd_stream_count--; > + > + if (fd->fd_stream_count == 0) { > + if (!IS_ERR(fd->rs_dma_buf)) > + mtk_fd_free_dma_handle(fd); > + > + rproc_shutdown(fd->rproc_handle); > + } > +} > + > +static int mtk_fd_hw_job_exec(struct mtk_fd_dev *fd, > + struct fd_enq_param *fd_param) > +{ > + struct ipi_message fd_ipi_msg; > + int ret; > + > + pm_runtime_get_sync((fd->dev)); > + > + reinit_completion(&fd->fd_irq_done); > + fd_ipi_msg.cmd_id = MTK_FD_IPI_CMD_ENQUEUE; > + memcpy(&fd_ipi_msg.fd_enq_param, fd_param, sizeof(struct fd_enq_param)); > + ret = scp_ipi_send(fd->scp_pdev, SCP_IPI_FD_CMD, &fd_ipi_msg, > + sizeof(fd_ipi_msg), MTK_FD_IPI_SEND_TIMEOUT); > + if (ret) { > + pm_runtime_put((fd->dev)); > + mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR); > + return ret; > + } > + return 0; > +} > + > +static int mtk_fd_vb2_buf_out_validate(struct vb2_buffer *vb) > +{ > + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); > + > + if (v4l2_buf->field == V4L2_FIELD_ANY) > + v4l2_buf->field = V4L2_FIELD_NONE; > + if (v4l2_buf->field != V4L2_FIELD_NONE) > + return -EINVAL; > + > + return 0; > +} > + > +static int mtk_fd_vb2_buf_prepare(struct vb2_buffer *vb) > +{ > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > + struct vb2_queue *vq = vb->vb2_queue; > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq); > + struct device *dev = ctx->dev; > + struct v4l2_pix_format_mplane *pixfmt; > + > + switch (vq->type) { > + case V4L2_BUF_TYPE_META_CAPTURE: > + if (vb2_plane_size(vb, 0) < ctx->dst_fmt.buffersize) { > + dev_dbg(dev, "meta size %d is too small\n"); > + return -EINVAL; > + } > + break; > + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: > + pixfmt = &ctx->src_fmt; > + > + if (vbuf->field == V4L2_FIELD_ANY) > + vbuf->field = V4L2_FIELD_NONE; > + > + if (vb->num_planes > 2 || vbuf->field != V4L2_FIELD_NONE) { > + dev_dbg(dev, "plane or field %d not supported\n", > + vb->num_planes, vbuf->field); > + return -EINVAL; > + } > + if (vb2_plane_size(vb, 0) < pixfmt->plane_fmt[0].sizeimage) { > + dev_dbg(dev, "plane %d is too small\n"); > + return -EINVAL; > + } > + break; > + } > + > + return 0; > +} > + > +static void mtk_fd_vb2_buf_queue(struct vb2_buffer *vb) > +{ > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > + > + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); > +} > + > +static int mtk_fd_vb2_queue_setup(struct vb2_queue *vq, > + unsigned int *num_buffers, > + unsigned int *num_planes, > + unsigned int sizes[], > + struct device *alloc_devs[]) > +{ > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq); > + unsigned int size[2]; > + unsigned int plane; > + > + switch (vq->type) { > + case V4L2_BUF_TYPE_META_CAPTURE: > + size[0] = ctx->dst_fmt.buffersize; > + break; > + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: > + size[0] = ctx->src_fmt.plane_fmt[0].sizeimage; > + if (*num_planes == 2) > + size[1] = ctx->src_fmt.plane_fmt[1].sizeimage; > + break; > + } > + > + if (*num_planes > 2) > + return -EINVAL; > + if (*num_planes == 0) { > + if (vq->type == V4L2_BUF_TYPE_META_CAPTURE) { > + sizes[0] = ctx->dst_fmt.buffersize; > + *num_planes = 1; > + return 0; > + } > + > + *num_planes = ctx->src_fmt.num_planes; > + for (plane = 0; plane < *num_planes; plane++) > + sizes[plane] = ctx->src_fmt.plane_fmt[plane].sizeimage; > + return 0; > + } > + > + for (plane = 0; plane < *num_planes; plane++) { > + if (sizes[plane] < size[plane]) > + return -EINVAL; > + } > + return 0; > +} > + > +static int mtk_fd_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) > +{ > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq); > + > + if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + return mtk_fd_hw_connect(ctx->fd_dev); > + else > + return 0; > +} > + > +static int mtk_fd_job_abort(struct mtk_fd_dev *fd) > +{ > + u32 ret; > + > + ret = wait_for_completion_timeout(&fd->fd_irq_done, > + msecs_to_jiffies(MTK_FD_HW_TIMEOUT)); > + /* Reset FD HW */ > + if (!ret) { > + struct ipi_message fd_ipi_msg; > + > + fd_ipi_msg.cmd_id = MTK_FD_IPI_CMD_RESET; > + if (scp_ipi_send(fd->scp_pdev, SCP_IPI_FD_CMD, &fd_ipi_msg, > + sizeof(fd_ipi_msg), MTK_FD_IPI_SEND_TIMEOUT)) > + dev_err(fd->dev, "FD Reset HW error\n"); > + return -ETIMEDOUT; > + } > + return 0; > +} > + Continue the discussion about job abort in RFC v2, I think the job_abort callback in v4l2_m2m_ops() might be useful. ref: https://elixir.bootlin.com/linux/v5.4-rc2/source/drivers/media/v4l2-core/v4l2-mem2mem.c#L398 https://elixir.bootlin.com/linux/v5.4-rc2/source/include/media/v4l2-mem2mem.h#L43 in drivers/media/v4l2-core/v4l2-mem2mem.c #398 v4l2_m2m_cancel_job() ... if (m2m_ctx->job_flags & TRANS_RUNNING) { spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); if (m2m_dev->m2m_ops->job_abort) m2m_dev->m2m_ops->job_abort(m2m_ctx->priv); dprintk("m2m_ctx %p running, will wait to complete\n", m2m_ctx); wait_event(m2m_ctx->finished, !(m2m_ctx->job_flags & TRANS_RUNNING)); } ... If this operation is set, we might use the v4l2_m2m_cancel_job() when suspend, and it will do mtk_fd_job_abort() here. > +static void mtk_fd_vb2_stop_streaming(struct vb2_queue *vq) > +{ > + struct mtk_fd_ctx *ctx = vb2_get_drv_priv(vq); > + struct mtk_fd_dev *fd = ctx->fd_dev; > + struct vb2_v4l2_buffer *vb; > + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; > + struct v4l2_m2m_queue_ctx *queue_ctx; > + > + mtk_fd_job_abort(fd); > + queue_ctx = V4L2_TYPE_IS_OUTPUT(vq->type) ? > + &m2m_ctx->out_q_ctx : > + &m2m_ctx->cap_q_ctx; > + while ((vb = v4l2_m2m_buf_remove(queue_ctx))) > + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); > + > + if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + mtk_fd_hw_disconnect(fd); > +} > + [snip] > +struct v4l2_ctrl_config mtk_fd_controls[] = { > + { > + .id = V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH, > + .name = "FD scale image widths", > + .type = V4L2_CTRL_TYPE_U16, > + .min = MTK_FD_OUTPUT_MIN_WIDTH, > + .max = MTK_FD_OUTPUT_MAX_WIDTH, > + .step = 1, > + .def = MTK_FD_OUTPUT_MAX_WIDTH, > + .dims = { MTK_FD_SCALE_ARR_NUM }, > + }, > + { > + .id = V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT, > + .name = "FD scale image heights", > + .type = V4L2_CTRL_TYPE_U16, > + .min = MTK_FD_OUTPUT_MIN_HEIGHT, > + .max = MTK_FD_OUTPUT_MAX_HEIGHT, > + .step = 1, > + .def = MTK_FD_OUTPUT_MAX_HEIGHT, > + .dims = { MTK_FD_SCALE_ARR_NUM }, > + }, > + { > + .id = V4L2_CID_MTK_FD_SCALE_IMG_NUM, > + .name = "FD scale size counts", > + .type = V4L2_CTRL_TYPE_INTEGER, > + .min = 0, > + .max = MTK_FD_SCALE_ARR_NUM, > + .step = 1, > + .def = 0, > + }, > + { > + .id = V4L2_CID_MTK_FD_DETECT_POSE, > + .name = "FD detect face angle and directions", > + .type = V4L2_CTRL_TYPE_U16, > + .min = 0, > + .max = 0xffff, > + .step = 1, > + .def = 0x3ff, > + .dims = { MTK_FD_FACE_ANGLE_NUM}, > + }, > + { > + .id = V4L2_CID_MTK_FD_DETECT_SPEED, > + .name = "FD detection speedup", > + .type = V4L2_CTRL_TYPE_INTEGER, > + .min = 0, > + .max = MTK_FD_MAX_SPEEDUP, > + .step = 1, > + .def = 0, > + }, > + { > + .id = V4L2_CID_MTK_FD_DETECTION_MODEL, > + .name = "FD use extra model", > + .type = V4L2_CTRL_TYPE_BOOLEAN, > + .min = 0, > + .max = 1, > + .step = 1, > + .def = 0, > + }, > +}; > + [snip] > +static int mtk_vfd_release(struct file *filp) > +{ > + struct mtk_fd_ctx *ctx = container_of(filp->private_data, > + struct mtk_fd_ctx, fh); > + > + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); > + > + v4l2_ctrl_handler_free(&ctx->hdl); > + v4l2_fh_del(&ctx->fh); > + v4l2_fh_exit(&ctx->fh); > + > + kfree(ctx); > + > + return 0; > +} > + > +static const struct v4l2_file_operations fd_video_fops = { > + .owner = THIS_MODULE, > + .open = mtk_vfd_open, > + .release = mtk_vfd_release, > + .poll = v4l2_m2m_fop_poll, > + .unlocked_ioctl = video_ioctl2, > + .mmap = v4l2_m2m_fop_mmap, > +#ifdef CONFIG_COMPAT > + .compat_ioctl32 = v4l2_compat_ioctl32, > +#endif > + > +}; > + > +static void mtk_fd_fill_user_param(struct user_param *user_param, > + struct v4l2_ctrl_handler *hdl) > +{ > + struct v4l2_ctrl *ctrl; > + int i; > + > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH); > + if (ctrl) > + for (i = 0; i < ctrl->elems; i++) > + user_param->scale_img_width[i] = ctrl->p_new.p_u16[i]; > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT); > + if (ctrl) > + for (i = 0; i < ctrl->elems; i++) > + user_param->scale_img_height[i] = ctrl->p_new.p_u16[i]; > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_SCALE_IMG_NUM); > + if (ctrl) > + user_param->scale_img_num = ctrl->val; > + > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECT_POSE); > + if (ctrl) > + for (i = 0; i < ctrl->elems; i++) > + user_param->face_directions[i] = ctrl->p_new.p_u16[i]; > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECT_SPEED); > + if (ctrl) > + user_param->fd_speedup = ctrl->val; > + ctrl = v4l2_ctrl_find(hdl, V4L2_CID_MTK_FD_DETECTION_MODEL); > + if (ctrl) > + user_param->fd_extra_model = ctrl->val; > +} > + > +static void mtk_fd_device_run(void *priv) > +{ > + struct mtk_fd_ctx *ctx = priv; > + struct mtk_fd_dev *fd = ctx->fd_dev; > + struct vb2_v4l2_buffer *src_buf, *dst_buf; > + struct fd_enq_param fd_param; > + > + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); > + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); > + > + fd_param.src_img[0].dma_addr = > + vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); > + fd_param.user_result.dma_addr = > + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); > + fd_param.output_vaddr = (u64)vb2_plane_vaddr(&dst_buf->vb2_buf, 0); > + fd_param.user_param.src_img_fmt = > + get_fd_img_fmt(ctx->src_fmt.pixelformat); > + if (ctx->src_fmt.num_planes == 2) > + fd_param.src_img[1].dma_addr = > + vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 1); > + mtk_fd_fill_user_param(&fd_param.user_param, &ctx->hdl); > + > + /* Complete request controls if any */ > + v4l2_ctrl_request_complete(src_buf->vb2_buf.req_obj.req, &ctx->hdl); > + > + fd->output = (struct fd_user_output *)fd_param.output_vaddr; > + mtk_fd_hw_job_exec(fd, &fd_param); > +} > + > +static struct v4l2_m2m_ops fd_m2m_ops = { > + .device_run = mtk_fd_device_run, > +}; > + > +static const struct media_device_ops fd_m2m_media_ops = { > + .req_validate = vb2_request_validate, > + .req_queue = v4l2_m2m_request_queue, > +}; [snip] > + > +static int mtk_fd_suspend(struct device *dev) > +{ > + struct mtk_fd_dev *fd = dev_get_drvdata(dev); > + > + if (pm_runtime_suspended(dev)) > + return 0; > + > + if (fd->fd_stream_count) > + if (mtk_fd_job_abort(fd)) > + mtk_fd_hw_job_finish(fd, VB2_BUF_STATE_ERROR); > + To avoid mtk_fd_hw_job_finish() trigger the next job, I suppose that we could use v4l2_m2m_cancel_job instead of job_abort and job_finish here. /** * v4l2_m2m_cancel_job() - cancel pending jobs for the context * @m2m_ctx: m2m context with jobs to be canceled * * In case of streamoff or release called on any context, * 1] If the context is currently running, then abort job will be called * 2] If the context is queued, then the context will be removed from * the job_queue */ or another way, we may add a flag and implement mtk_fd_job_ready() that reads the flag if we suspend, we set the flag and do job_abort and job_finish, even if it try enqueue, it will still not really queue the job, until we reset the flag in mtk_fd_resume(). how do you think? > + /* suspend FD HW */ > + writel(0x0, fd->fd_base + MTK_FD_REG_OFFSET_INT_EN); > + writel(0x0, fd->fd_base + MTK_FD_REG_OFFSET_HW_ENABLE); > + clk_disable_unprepare(fd->fd_clk); > + dev_dbg(dev, "%s:disable clock\n", __func__); > + > + return 0; > +} > + > +static int mtk_fd_resume(struct device *dev) > +{ > + struct mtk_fd_dev *fd = dev_get_drvdata(dev); > + int ret; > + > + if (pm_runtime_suspended(dev)) > + return 0; > + > + ret = clk_prepare_enable(fd->fd_clk); > + if (ret < 0) { > + dev_dbg(dev, "Failed to open fd clk:%d\n", ret); > + return ret; > + } > + > + /* resume FD HW */ > + writel(MTK_FD_SET_HW_ENABLE, fd->fd_base + MTK_FD_REG_OFFSET_HW_ENABLE); > + writel(0x1, fd->fd_base + MTK_FD_REG_OFFSET_INT_EN); > + dev_dbg(dev, "%s:enable clock\n", __func__); > + > + return 0; > +} > + > +static int mtk_fd_runtime_suspend(struct device *dev) > +{ > + struct mtk_fd_dev *fd = dev_get_drvdata(dev); > + > + clk_disable_unprepare(fd->fd_clk); > + return 0; > +} > + > +static int mtk_fd_runtime_resume(struct device *dev) > +{ > + struct mtk_fd_dev *fd = dev_get_drvdata(dev); > + int ret; > + > + ret = clk_prepare_enable(fd->fd_clk); > + if (ret < 0) { > + dev_err(dev, "Failed to open fd clk:%d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static const struct dev_pm_ops mtk_fd_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(mtk_fd_suspend, mtk_fd_resume) > + SET_RUNTIME_PM_OPS(mtk_fd_runtime_suspend, mtk_fd_runtime_resume, NULL) > +}; > + > +static const struct of_device_id mtk_fd_of_ids[] = { > + { .compatible = "mediatek,mt8183-fd", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, mtk_fd_of_ids); > + > +static struct platform_driver mtk_fd_driver = { > + .probe = mtk_fd_probe, > + .remove = mtk_fd_remove, > + .driver = { > + .name = "mtk-fd-4.0", > + .of_match_table = of_match_ptr(mtk_fd_of_ids), > + .pm = &mtk_fd_pm_ops, > + } > +}; > +module_platform_driver(mtk_fd_driver); > + > +MODULE_DESCRIPTION("Mediatek FD driver"); > +MODULE_LICENSE("GPL"); > diff --git a/include/uapi/linux/mtk-fd-v4l2-controls.h b/include/uapi/linux/mtk-fd-v4l2-controls.h > new file mode 100644 > index 000000000000..9b814de05b0c > --- /dev/null > +++ b/include/uapi/linux/mtk-fd-v4l2-controls.h > @@ -0,0 +1,69 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +// > +// Copyright (c) 2019 MediaTek Inc. > +/* > + * For V4L2_CID_MTK_FD_DETECT_POSE, User can set the desired face direction to > + * be detected for each face angle, there are five face angle and 12 directions. > + * Below shows the definition of face angle and face direction, > + * and a recommended usage of for face detection, the more selected directions > + * the longer HW process time needed. > + * > + * enum face_angle { > + * MTK_FD_FACE_FRONT, > + * MTK_FD_FACE_RIGHT_50, > + * MTK_FD_FACE_LEFT_50, > + * MTK_FD_FACE_RIGHT_90, > + * MTK_FD_FACE_LEFT_90, > + * MTK_FD_FACE_ANGLE_NUM, > + * }; > + * > + * struct face_direction_def { > + * __u16 MTK_FD_FACE_DIR_0 : 1, > + * MTK_FD_FACE_DIR_30 : 1, > + * MTK_FD_FACE_DIR_60 : 1, > + * MTK_FD_FACE_DIR_90 : 1, > + * MTK_FD_FACE_DIR_120 : 1, > + * MTK_FD_FACE_DIR_150 : 1, > + * MTK_FD_FACE_DIR_180 : 1, > + * MTK_FD_FACE_DIR_210 : 1, > + * MTK_FD_FACE_DIR_240 : 1, > + * MTK_FD_FACE_DIR_270 : 1, > + * MTK_FD_FACE_DIR_300 : 1, > + * MTK_FD_FACE_DIR_330 : 1, > + * : 4; > + * }; > + * > + * Sample usage: > + * u16 face_directions[MTK_FD_FACE_ANGLE_NUM] = {0}; > + * > + * face_directions[MTK_FD_FACE_FRONT] = 0x3ff; > + * > + */ > + > +#ifndef __UAPI_MTK_FD_V4L2_CONTROLS_H__ > +#define __UAPI_MTK_FD_V4L2_CONTROLS_H__ > + > +#include <linux/v4l2-controls.h> > + > +/* Set the face angle and directions to be detected */ > +#define V4L2_CID_MTK_FD_DETECT_POSE (V4L2_CID_USER_MTK_FD_BASE + 1) > + > +/* Set image widths for an input image to be scaled down for face detection */ > +#define V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH (V4L2_CID_USER_MTK_FD_BASE + 2) > + > +/* Set image heights for an input image to be scaled down for face detection */ > +#define V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT (V4L2_CID_USER_MTK_FD_BASE + 3) > + > +/* Set the length of scale down size array */ > +#define V4L2_CID_MTK_FD_SCALE_IMG_NUM (V4L2_CID_USER_MTK_FD_BASE + 4) > + > +/* Set the detection speed, usually reducing accuracy. */ > +#define V4L2_CID_MTK_FD_DETECT_SPEED (V4L2_CID_USER_MTK_FD_BASE + 5) > + > +/* Select the detection model or algorithm to be used. */ > +#define V4L2_CID_MTK_FD_DETECTION_MODEL (V4L2_CID_USER_MTK_FD_BASE + 6) > + > +/* We reserve 16 controls for this driver. */ > +#define V4L2_CID_MTK_FD_MAX 16 > + For these control IDs, I think the following should be remained as chip specific controls. V4L2_CID_MTK_FD_SCALE_DOWN_IMG_WIDTH, V4L2_CID_MTK_FD_SCALE_DOWN_IMG_HEIGHT and V4L2_CID_MTK_FD_SCALE_IMG_NUM Hope there would be standardizing face detection api that cover the rest controls: V4L2_CID_MTK_FD_DETECT_POSE, V4L2_CID_MTK_FD_DETECT_SPEED and V4L2_CID_MTK_FD_DETECTION_MODEL Would you have any suggestions on how to propose the standard face detection apis? Thanks and best regards Jerry > +#endif /* __UAPI_MTK_FD_V4L2_CONTROLS_H__ */ > diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h > index 3dcfc6148f99..eae876ea6d0a 100644 > --- a/include/uapi/linux/v4l2-controls.h > +++ b/include/uapi/linux/v4l2-controls.h > @@ -192,6 +192,10 @@ enum v4l2_colorfx { > * We reserve 16 controls for this driver. */ > #define V4L2_CID_USER_IMX_BASE (V4L2_CID_USER_BASE + 0x10b0) > > +/* The base for the mediatek FD driver controls */ > +/* We reserve 16 controls for this driver. */ > +#define V4L2_CID_USER_MTK_FD_BASE (V4L2_CID_USER_BASE + 0x10d0) > + > /* MPEG-class control IDs */ > /* The MPEG controls are applicable to all codec controls > * and the 'MPEG' part of the define is historical */