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/mtk-isp/Makefile | 16 + drivers/media/platform/mtk-isp/fd/Makefile | 38 + drivers/media/platform/mtk-isp/fd/mtk_fd-core.h | 157 +++ drivers/media/platform/mtk-isp/fd/mtk_fd-ctx.h | 299 ++++++ .../platform/mtk-isp/fd/mtk_fd-dev-ctx-core.c | 912 +++++++++++++++++ drivers/media/platform/mtk-isp/fd/mtk_fd-dev.c | 355 +++++++ drivers/media/platform/mtk-isp/fd/mtk_fd-dev.h | 198 ++++ .../media/platform/mtk-isp/fd/mtk_fd-smem-drv.c | 452 +++++++++ drivers/media/platform/mtk-isp/fd/mtk_fd-smem.h | 25 + .../media/platform/mtk-isp/fd/mtk_fd-v4l2-util.c | 1046 ++++++++++++++++++++ drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.c | 114 +++ drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.h | 36 + drivers/media/platform/mtk-isp/fd/mtk_fd.c | 730 ++++++++++++++ drivers/media/platform/mtk-isp/fd/mtk_fd.h | 127 +++ 14 files changed, 4505 insertions(+) create mode 100644 drivers/media/platform/mtk-isp/Makefile create mode 100644 drivers/media/platform/mtk-isp/fd/Makefile create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-core.h create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-ctx.h create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-dev-ctx-core.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-dev.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-dev.h create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-smem-drv.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-smem.h create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2-util.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.h create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd.c create mode 100644 drivers/media/platform/mtk-isp/fd/mtk_fd.h diff --git a/drivers/media/platform/mtk-isp/Makefile b/drivers/media/platform/mtk-isp/Makefile new file mode 100644 index 0000000..5e3a9aa --- /dev/null +++ b/drivers/media/platform/mtk-isp/Makefile @@ -0,0 +1,16 @@ +# +# Copyright (C) 2018 MediaTek Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License 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. +# + +ifeq ($(CONFIG_VIDEO_MEDIATEK_FD_SUPPORT),y) +obj-y += fd/ +endif diff --git a/drivers/media/platform/mtk-isp/fd/Makefile b/drivers/media/platform/mtk-isp/fd/Makefile new file mode 100644 index 0000000..ac168c1 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/Makefile @@ -0,0 +1,38 @@ +# +# Copyright (C) 2018 MediaTek Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License 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. +# + +$(info "FD: makefile start srctree: $(srctree)") +ccflags-y += -I$(srctree)/drivers/media/platform/mtk-vpu +ccflags-y += -I$(srctree)/drivers/media/platform/mtk-isp/fd +#ccflags-y += -I$(srctree)/drivers/media/platform/mtk-isp/common + +obj-y += mtk_fd.o +obj-y += mtk_fd-v4l2.o + +# To provide alloc context managing memory shared +# between CPU and FD coprocessor +mtk_fd_smem-objs := \ +mtk_fd-smem-drv.o + +obj-y += mtk_fd_smem.o + +# Utilits to provide frame-based streaming model +# with v4l2 user interfaces +mtk_fd_util-objs := \ +mtk_fd-dev.o \ +mtk_fd-v4l2-util.o \ +mtk_fd-dev-ctx-core.o + +obj-y += mtk_fd_util.o + +$(info "FD: makefile end") diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-core.h b/drivers/media/platform/mtk-isp/fd/mtk_fd-core.h new file mode 100644 index 0000000..a7c1e1d --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-core.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2015 MediaTek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 __MTK_FD_CORE_H__ +#define __MTK_FD_CORE_H__ + +#define SIG_ERESTARTSYS 512 + +#ifndef CONFIG_MTK_CLKMGR +#include <linux/clk.h> +#endif + +#ifdef CONFIG_MTK_CLKMGR +#include <mach/mt_clkmgr.h> +#endif + +#include "mtk_fd-dev.h" +#include "mtk_fd-ctx.h" + +#include <linux/io.h> + +#define FD_WR32(v, a) \ +do { \ + __raw_writel((v), (void __force __iomem *)((a))); \ + mb(); /* ensure written */ \ +} while (0) + +#define FD_RD32(addr) ioread32((void *)addr) + +#define FD_INT_EN (0x15c) +#define FD_INT (0x168) +#define FD_RESULT (0x178) +#define FD_IRQ_MASK (0x001) + +#define RS_BUF_SIZE_MAX (2288788) +#define VA_OFFSET (0xffff000000000000) + +enum fd_irq { + FD_IRQ_IDX = 0, + FD_IRQ_IDX_NUM +}; + +enum fd_state { + FD_INI, + FD_ENQ, + FD_CBD, +}; + +enum stream_stat { + STREAM_OFF, + STREAM_ON, +}; + +struct ipi_fd_enq_param { + u8 source_img_fmt; + struct fd_buffer output_addr; + struct fd_buffer src_y; + struct fd_buffer src_uv; + struct fd_buffer config_addr; +} __packed; + +struct fd_manager_ctx { + struct fd_buffer learn_data_buf[2][LEARNDATA_NUM]; + struct fd_buffer fd_config; + struct fd_buffer rs_config; + struct fd_buffer fd_result; + struct fd_buffer rs_result; + struct fd_buffer src_img; +} __packed; + +struct mtk_fd_drv_ctx { + struct sg_table sgtable; + u32 frame_id; + struct fd_buffer rs_result; + struct fd_buffer scp_mem; + struct platform_device *vpu_pdev; + + atomic_t fd_enque_cnt; + atomic_t fd_stream_cnt; + atomic_t fd_user_cnt; +}; + +struct mtk_fd_drv_dev { + struct platform_device *pdev; + + dev_t fd_devno; + struct cdev fd_cdev; + struct class *fd_class; + struct mtk_fd_drv_ctx fd_ctx; + + struct device *larb_dev; + struct clk *fd_clk; + enum fd_state state; + enum stream_stat streaming; + + wait_queue_head_t wq; + u32 fd_irq_result; + + void __iomem *fd_base; +}; + +struct mtk_isp_fd_drv_data { + struct mtk_fd_dev fd_dev; + struct mtk_fd_drv_dev fd_hw_dev; +} __packed; + +static inline struct mtk_fd_drv_dev *get_fd_hw_device(struct device *dev) +{ + struct mtk_isp_fd_drv_data *drv_data = + dev_get_drvdata(dev); + if (drv_data) + return &drv_data->fd_hw_dev; + else + return NULL; +} + +#define mtk_fd_us_to_jiffies(us) \ + ((((unsigned long)(us) / 1000) * HZ + 512) >> 10) + +#define mtk_fd_hw_dev_to_drv(__fd_hw_dev) \ + container_of(__fd_hw_dev, \ + struct mtk_isp_fd_drv_data, fd_hw_dev) + +#define mtk_fd_ctx_to_drv(__fd_ctx) \ + container_of(__fd_ctx, \ + struct mtk_isp_fd_drv_data, fd_hw_dev.fd_ctx) + +enum fd_scp_cmd { + FD_CMD_INIT, + FD_CMD_ENQ, + FD_CMD_EXIT, +}; + +enum fd_clk { + clock_off = 0, + clock_on, +}; + +struct ipi_message { + u8 cmd_id; + union { + struct fd_buffer fd_manager; + struct v4l2_fd_param fd_param; + }; +} __packed; + +#endif/*__MTK_FD_CORE_H__*/ diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-ctx.h b/drivers/media/platform/mtk-isp/fd/mtk_fd-ctx.h new file mode 100644 index 0000000..d78a3af --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-ctx.h @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 __MTK_FD_CTX_H__ +#define __MTK_FD_CTX_H__ + +#include <linux/types.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/videobuf2-core.h> +#include <media/v4l2-subdev.h> + +#define MTK_FD_CTX_QUEUES (16) +#define MTK_FD_CTX_FRAME_BUNDLE_BUFFER_MAX (MTK_FD_CTX_QUEUES) +#define MTK_FD_CTX_DESC_MAX (MTK_FD_CTX_QUEUES) + +#define MTK_FD_CTX_MODE_DEBUG_OFF (0) +#define MTK_FD_CTX_MODE_DEBUG_BYPASS_JOB_TRIGGER (1) +#define MTK_FD_CTX_MODE_DEBUG_BYPASS_ALL (2) + +#define MTK_FD_GET_CTX_ID_FROM_SEQUENCE(sequence) \ + ((sequence) >> 16 & 0x0000FFFF) + +#define MTK_FD_CTX_META_BUF_DEFAULT_SIZE (1110 * 1024) + +struct mtk_fd_ctx; +struct mtk_fd_ctx_finish_param; + +/** + * Attributes setup by device context owner + */ +struct mtk_fd_ctx_queue_desc { + int id; + /* id of the context queue */ + char *name; + /* Will be exported to media entity name */ + int capture; + /** + * 1 for capture queue (device to user), + * 0 for output queue (from user to device) + */ + int image; + /* 1 for image, 0 for meta data */ + unsigned int dma_port; + /*The dma port associated to the buffer*/ + struct mtk_fd_ctx_format *fmts; + int num_fmts; + /* Default format of this queue */ + int default_fmt_idx; +}; + +/** + * Supported format and the information used for + * size calculation + */ +struct mtk_fd_ctx_meta_format { + u32 dataformat; + u32 max_buffer_size; + u8 flags; +}; + +/** + * MDP module's private format definitation + * (the same as struct mdp_format) + * It will be removed and changed to MDP's external interface + * after the integration with MDP module. + */ +struct mtk_fd_ctx_mdp_format { + u32 pixelformat; + u32 mdp_color; + u8 depth[VIDEO_MAX_PLANES]; + u8 row_depth[VIDEO_MAX_PLANES]; + u8 num_planes; + u8 walign; + u8 halign; + u8 salign; + u32 flags; +}; + +struct mtk_fd_ctx_format { + union { + struct mtk_fd_ctx_meta_format meta; + struct mtk_fd_ctx_mdp_format img; + } fmt; +}; + +union mtk_v4l2_fmt { + struct v4l2_pix_format_mplane pix_mp; + struct v4l2_meta_format meta; +}; + +/* Attributes setup by device context owner */ +struct mtk_fd_ctx_queues_setting { + int master; + /* The master input node to trigger the frame data enqueue */ + struct mtk_fd_ctx_queue_desc *output_queue_descs; + int total_output_queues; + struct mtk_fd_ctx_queue_desc *capture_queue_descs; + int total_capture_queues; +}; + +struct mtk_fd_ctx_queue_attr { + int master; + int input_offset; + int total_num; +}; + +/** + * Video node context. Since we use + * mtk_fd_ctx_frame_bundle to manage enqueued + * buffers by frame now, we don't use bufs filed of + * mtk_fd_ctx_queue now + */ +struct mtk_fd_ctx_queue { + union mtk_v4l2_fmt fmt; + struct mtk_fd_ctx_format *ctx_fmt; + + unsigned int width_pad; + /* bytesperline, reserved */ + struct mtk_fd_ctx_queue_desc desc; + unsigned int buffer_usage; + /* Current buffer usage of the queue */ + int rotation; + struct list_head bufs; + /* Reserved, not used now */ +}; + +enum mtk_fd_ctx_frame_bundle_state { + MTK_FD_CTX_FRAME_NEW, + /* Not allocated */ + MTK_FD_CTX_FRAME_PREPARED, + /* Allocated but has not be processed */ + MTK_FD_CTX_FRAME_PROCESSING, + /* Queued, waiting to be filled */ +}; + +/** + * The definiation is compatible with FD driver's state definiation + * currently and will be decoupled after further integration + */ +enum mtk_fd_ctx_frame_data_state { + MTK_FD_CTX_FRAME_DATA_EMPTY = 0, /* FRAME_STATE_INIT */ + MTK_FD_CTX_FRAME_DATA_DONE = 3, /* FRAME_STATE_DONE */ + MTK_FD_CTX_FRAME_DATA_STREAMOFF_DONE = 4, /*FRAME_STATE_STREAMOFF*/ + MTK_FD_CTX_FRAME_DATA_ERROR = 5, /*FRAME_STATE_ERROR*/ +}; + +struct mtk_fd_ctx_frame_bundle { + struct mtk_fd_ctx_buffer* + buffers[MTK_FD_CTX_FRAME_BUNDLE_BUFFER_MAX]; + int id; + int num_img_capture_bufs; + int num_img_output_bufs; + int num_meta_capture_bufs; + int num_meta_output_bufs; + int last_index; + int state; + struct list_head list; +}; + +struct mtk_fd_ctx_frame_bundle_list { + struct list_head list; +}; + +struct mtk_fd_ctx { + struct platform_device *pdev; + struct platform_device *smem_device; + unsigned short ctx_id; + char device_name[12]; + const struct mtk_fd_ctx_ops *ops; + struct mtk_fd_dev_node_mapping *mtk_fd_dev_node_map; + unsigned int dev_node_num; + struct mtk_fd_ctx_queue queue[MTK_FD_CTX_QUEUES]; + struct mtk_fd_ctx_queue_attr queues_attr; + atomic_t frame_param_sequence; + int streaming; + void *img_vb2_alloc_ctx; + void *smem_vb2_alloc_ctx; + struct v4l2_subdev_fh *fh; + struct mtk_fd_ctx_frame_bundle frame_bundles[VB2_MAX_FRAME]; + struct mtk_fd_ctx_frame_bundle_list processing_frames; + struct mtk_fd_ctx_frame_bundle_list free_frames; + int enabled_dma_ports; + int num_frame_bundle; + int mode; /* Reserved for debug */ + spinlock_t qlock; +}; + +enum mtk_fd_ctx_buffer_state { + MTK_FD_CTX_BUFFER_NEW, + MTK_FD_CTX_BUFFER_PROCESSING, + MTK_FD_CTX_BUFFER_DONE, + MTK_FD_CTX_BUFFER_FAILED, +}; + +struct mtk_fd_ctx_buffer { + union mtk_v4l2_fmt fmt; + struct mtk_fd_ctx_format *ctx_fmt; + int capture; + int image; + int frame_id; + int user_sequence; /* Sequence number assigned by user */ + dma_addr_t daddr; + void *vaddr; + phys_addr_t paddr; + unsigned int queue; + unsigned int buffer_usage; + enum mtk_fd_ctx_buffer_state state; + int rotation; + struct list_head list; +}; + +struct mtk_fd_ctx_desc { + char *proc_dev_phandle; + /* The context device's compatble string name in device tree*/ + int (*init)(struct mtk_fd_ctx *ctx); + /* configure the core functions of the device context */ +}; + +struct mtk_fd_ctx_init_table { + int total_dev_ctx; + struct mtk_fd_ctx_desc *ctx_desc_tbl; +}; + +struct mtk_fd_ctx_finish_param { + unsigned int frame_id; + u64 timestamp; + unsigned int state; +}; + +bool mtk_fd_ctx_is_streaming(struct mtk_fd_ctx *ctx); + +int mtk_fd_ctx_core_job_finish(struct mtk_fd_ctx *ctx, + struct mtk_fd_ctx_finish_param *param); + +int mtk_fd_ctx_core_init(struct mtk_fd_ctx *ctx, + struct platform_device *pdev, int ctx_id, + struct mtk_fd_ctx_desc *ctx_desc, + struct platform_device *proc_pdev, + struct platform_device *smem_pdev); + +int mtk_fd_ctx_core_exit(struct mtk_fd_ctx *ctx); + +void mtk_fd_ctx_buf_init(struct mtk_fd_ctx_buffer *b, unsigned int queue, + dma_addr_t daddr); + +enum mtk_fd_ctx_buffer_state + mtk_fd_ctx_get_buffer_state(struct mtk_fd_ctx_buffer *b); + +int mtk_fd_ctx_next_global_frame_sequence(struct mtk_fd_ctx *ctx, int locked); + +int mtk_fd_ctx_core_queue_setup + (struct mtk_fd_ctx *ctx, + struct mtk_fd_ctx_queues_setting *queues_setting); + +int mtk_fd_ctx_core_finish_param_init(void *param, int frame_id, int state); + +int mtk_fd_ctx_finish_frame(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *frame_bundle, + int done); + +int mtk_fd_ctx_frame_bundle_init(struct mtk_fd_ctx_frame_bundle *frame_bundle); + +void mtk_fd_ctx_frame_bundle_add(struct mtk_fd_ctx *ctx, + struct mtk_fd_ctx_frame_bundle *bundle, + struct mtk_fd_ctx_buffer *ctx_buf); + +int mtk_fd_ctx_trigger_job(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *bundle_data); + +int mtk_fd_ctx_fmt_set_img(struct mtk_fd_ctx *dev_ctx, int queue_id, + struct v4l2_pix_format_mplane *user_fmt, + struct v4l2_pix_format_mplane *node_fmt); + +int mtk_fd_ctx_fmt_set_meta(struct mtk_fd_ctx *dev_ctx, int queue_id, + struct v4l2_meta_format *user_fmt, + struct v4l2_meta_format *node_fmt); + +int mtk_fd_ctx_format_load_default_fmt(struct mtk_fd_ctx_queue *queue, + struct v4l2_format *fmt_to_fill); + +int mtk_fd_ctx_streamon(struct mtk_fd_ctx *dev_ctx); +int mtk_fd_ctx_streamoff(struct mtk_fd_ctx *dev_ctx); +int mtk_fd_ctx_release(struct mtk_fd_ctx *dev_ctx); +int mtk_fd_ctx_open(struct mtk_fd_ctx *dev_ctx); +int mtk_fd_ctx_fd_init(struct mtk_fd_ctx *ctx); + +#endif /*__MTK_FD_CTX_H__*/ diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-dev-ctx-core.c b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev-ctx-core.c new file mode 100644 index 0000000..7314436 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev-ctx-core.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <media/videobuf2-dma-contig.h> +#include <linux/dma-mapping.h> + +#include "mtk_fd.h" +#include "mtk_fd-dev.h" +#include "mtk_fd-smem.h" +#include "mtk_fd-v4l2.h" + +#if KERNEL_VERSION(4, 8, 0) >= MTK_FD_KERNEL_BASE_VERSION +#include <linux/dma-attrs.h> +#endif + +struct vb2_v4l2_buffer *mtk_fd_ctx_buffer_get_vb2_v4l2_buffer +(struct mtk_fd_ctx_buffer *ctx_buf) +{ + struct mtk_fd_dev_buffer *dev_buf = NULL; + + if (!ctx_buf) { + pr_err("Failed to convert ctx_buf to dev_buf: Null pointer\n"); + return NULL; + } + + dev_buf = mtk_fd_ctx_buf_to_dev_buf(ctx_buf); + + return &dev_buf->m2m2_buf.vbb; +} + +int mtk_fd_ctx_core_queue_setup(struct mtk_fd_ctx *ctx, + struct mtk_fd_ctx_queues_setting * + queues_setting) +{ + int queue_idx = 0; + int i = 0; + + for (i = 0; i < queues_setting->total_output_queues; i++) { + struct mtk_fd_ctx_queue_desc *queue_desc = + &queues_setting->output_queue_descs[i]; + ctx->queue[queue_idx].desc = *queue_desc; + queue_idx++; + } + + /* Setup the capture queue */ + for (i = 0; i < queues_setting->total_capture_queues; i++) { + struct mtk_fd_ctx_queue_desc *queue_desc = + &queues_setting->capture_queue_descs[i]; + ctx->queue[queue_idx].desc = *queue_desc; + queue_idx++; + } + + ctx->queues_attr.master = queues_setting->master; + ctx->queues_attr.total_num = queue_idx; + ctx->dev_node_num = ctx->queues_attr.total_num; + strcpy(ctx->device_name, MTK_FD_DEV_NAME); + return 0; +} + +/* Mediatek FD context core initialization */ +int mtk_fd_ctx_core_init(struct mtk_fd_ctx *ctx, + struct platform_device *pdev, int ctx_id, + struct mtk_fd_ctx_desc *ctx_desc, + struct platform_device *proc_pdev, + struct platform_device *smem_pdev) +{ + /* Initialize main data structure */ + int r = 0; + +#if KERNEL_VERSION(4, 8, 0) >= MTK_FD_KERNEL_BASE_VERSION + ctx->smem_vb2_alloc_ctx = + vb2_dma_contig_init_ctx(&smem_pdev->dev); + ctx->img_vb2_alloc_ctx = + vb2_dma_contig_init_ctx(&pdev->dev); +#else + ctx->smem_vb2_alloc_ctx = &smem_pdev->dev; + ctx->img_vb2_alloc_ctx = &pdev->dev; +#endif + if (IS_ERR((__force void *)ctx->smem_vb2_alloc_ctx)) + pr_err("Failed to alloc vb2 dma context: smem_vb2_alloc_ctx"); + + if (IS_ERR((__force void *)ctx->img_vb2_alloc_ctx)) + pr_err("Failed to alloc vb2 dma context: img_vb2_alloc_ctx"); + + ctx->pdev = pdev; + ctx->ctx_id = ctx_id; + /* keep th smem pdev to use related iommu functions */ + ctx->smem_device = smem_pdev; + + /* Will set default enabled after passing the unit test */ + ctx->mode = MTK_FD_CTX_MODE_DEBUG_OFF; + + /* initialized the global frame index of the device context */ + atomic_set(&ctx->frame_param_sequence, 0); + spin_lock_init(&ctx->qlock); + + /* setup the core operation of the device context */ + if (ctx_desc && ctx_desc->init) + r = ctx_desc->init(ctx); + + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_core_init); + +int mtk_fd_ctx_core_exit(struct mtk_fd_ctx *ctx) +{ +#if KERNEL_VERSION(4, 8, 0) >= MTK_FD_KERNEL_BASE_VERSION + vb2_dma_contig_cleanup_ctx(ctx->smem_vb2_alloc_ctx); + vb2_dma_contig_cleanup_ctx(ctx->img_vb2_alloc_ctx); +#else + ctx->smem_vb2_alloc_ctx = NULL; + ctx->img_vb2_alloc_ctx = NULL; +#endif + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_core_exit); + +int mtk_fd_ctx_next_global_frame_sequence(struct mtk_fd_ctx *ctx, int locked) +{ + int global_frame_sequence = + atomic_inc_return(&ctx->frame_param_sequence); + + if (!locked) + spin_lock(&ctx->qlock); + + global_frame_sequence = + (global_frame_sequence & 0x0000FFFF) | (ctx->ctx_id << 16); + + if (!locked) + spin_unlock(&ctx->qlock); + + return global_frame_sequence; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_next_global_frame_sequence); + +static void mtk_fd_ctx_buffer_done + (struct mtk_fd_ctx_buffer *ctx_buf, int state) +{ + if (!ctx_buf || state != MTK_FD_CTX_BUFFER_DONE || + state != MTK_FD_CTX_BUFFER_FAILED) + return; + + ctx_buf->state = state; +} + +struct mtk_fd_ctx_frame_bundle *mtk_fd_ctx_get_processing_frame +(struct mtk_fd_ctx *dev_ctx, int frame_id) +{ + struct mtk_fd_ctx_frame_bundle *frame_bundle = NULL; + + spin_lock(&dev_ctx->qlock); + + list_for_each_entry(frame_bundle, + &dev_ctx->processing_frames.list, list) { + if (frame_bundle->id == frame_id) { + spin_unlock(&dev_ctx->qlock); + return frame_bundle; + } + } + + spin_unlock(&dev_ctx->qlock); + + return NULL; +} + +/** + * structure mtk_fd_ctx_finish_param must be the first elemt of param + * So that the buffer can be return to vb2 queue successfully + */ +int mtk_fd_ctx_core_finish_param_init(void *param, int frame_id, int state) +{ + struct mtk_fd_ctx_finish_param *fram_param = + (struct mtk_fd_ctx_finish_param *)param; + fram_param->frame_id = frame_id; + fram_param->state = state; + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_core_finish_param_init); + +void mtk_fd_ctx_frame_bundle_add(struct mtk_fd_ctx *ctx, + struct mtk_fd_ctx_frame_bundle *bundle, + struct mtk_fd_ctx_buffer *ctx_buf) +{ + int queue_id = 0; + struct mtk_fd_ctx_queue *ctx_queue = NULL; + + if (!bundle || !ctx_buf) { + pr_warn("Add buffer to frame bundle failed, bundle(%llx),buf(%llx)\n", + (long long)bundle, (long long)ctx_buf); + return; + } + + queue_id = ctx_buf->queue; + + if (bundle->buffers[queue_id]) + pr_warn("Queue(%d) buffer has alreay in this bundle, overwrite happen\n", + queue_id); + + pr_debug("Add queue(%d) buffer%llx\n", + queue_id, (unsigned long long)ctx_buf); + bundle->buffers[queue_id] = ctx_buf; + + /* Fill context queue related information */ + ctx_queue = &ctx->queue[queue_id]; + + if (!ctx_queue) { + pr_err("Can't find ctx queue (%d)\n", queue_id); + return; + } + + if (ctx->queue[ctx_buf->queue].desc.image) { + if (ctx->queue[ctx_buf->queue].desc.capture) + bundle->num_img_capture_bufs++; + else + bundle->num_img_output_bufs++; + } else { + if (ctx->queue[ctx_buf->queue].desc.capture) + bundle->num_meta_capture_bufs++; + else + bundle->num_meta_output_bufs++; + } +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_frame_bundle_add); + +void mtk_fd_ctx_buf_init(struct mtk_fd_ctx_buffer *b, + unsigned int queue, dma_addr_t daddr) +{ + b->state = MTK_FD_CTX_BUFFER_NEW; + b->queue = queue; + b->daddr = daddr; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_buf_init); + +enum mtk_fd_ctx_buffer_state + mtk_fd_ctx_get_buffer_state(struct mtk_fd_ctx_buffer *b) +{ + return b->state; +} + +bool mtk_fd_ctx_is_streaming(struct mtk_fd_ctx *ctx) +{ + return ctx->streaming; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_is_streaming); + +static int mtk_fd_ctx_free_frame(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *frame_bundle) +{ + spin_lock(&dev_ctx->qlock); + + frame_bundle->state = MTK_FD_CTX_FRAME_NEW; + list_del(&frame_bundle->list); + list_add_tail(&frame_bundle->list, &dev_ctx->free_frames.list); + + spin_unlock(&dev_ctx->qlock); + + return 0; +} + +int mtk_fd_ctx_core_job_finish(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_finish_param *param) +{ + int i = 0; + struct platform_device *pdev = dev_ctx->pdev; + struct mtk_fd_ctx_finish_param *fram_param = + (struct mtk_fd_ctx_finish_param *)param; + struct mtk_fd_dev *fd_dev = NULL; + struct mtk_fd_ctx_frame_bundle *frame = NULL; + enum vb2_buffer_state vbf_state = VB2_BUF_STATE_DONE; + enum mtk_fd_ctx_buffer_state ctxf_state = + MTK_FD_CTX_BUFFER_DONE; + int user_sequence = 0; + + const int ctx_id = + MTK_FD_GET_CTX_ID_FROM_SEQUENCE(fram_param->frame_id); + u64 timestamp = 0; + + pr_debug("mtk_fd_ctx_core_job_finish_cb: param (%llx), platform_device(%llx)\n", + (unsigned long long)param, (unsigned long long)pdev); + + if (!dev_ctx) + pr_err("dev_ctx can't be null, can't release the frame\n"); + + fd_dev = mtk_fd_ctx_to_dev(dev_ctx); + + if (fram_param) { + dev_info(&fd_dev->pdev->dev, + "CB recvied from ctx(%d), frame(%d), state(%d), fd_dev(%llx)\n", + ctx_id, fram_param->frame_id, + fram_param->state, (long long)fd_dev); + } else { + dev_err(&fd_dev->pdev->dev, + "CB recvied from ctx(%d), frame param is NULL\n", + ctx_id); + return -EINVAL; + } + + /* Get the buffers of the processed frame */ + frame = mtk_fd_ctx_get_processing_frame(&fd_dev->ctx, + fram_param->frame_id); + + if (!frame) { + pr_err("Can't find the frame boundle, Frame(%d)\n", + fram_param->frame_id); + return -EINVAL; + } + + if (fram_param->state == MTK_FD_CTX_FRAME_DATA_ERROR) { + vbf_state = VB2_BUF_STATE_ERROR; + ctxf_state = MTK_FD_CTX_BUFFER_FAILED; + } + + /** + * Set the buffer's VB2 status so that + * the user can dequeue the buffer + */ + timestamp = ktime_get_ns(); + for (i = 0; i <= frame->last_index; i++) { + struct mtk_fd_ctx_buffer *ctx_buf = frame->buffers[i]; + + if (!ctx_buf) { + dev_dbg(&fd_dev->pdev->dev, + "ctx_buf(queue id= %d) of frame(%d)is NULL\n", + i, fram_param->frame_id); + continue; + } else { + struct vb2_v4l2_buffer *b = + mtk_fd_ctx_buffer_get_vb2_v4l2_buffer(ctx_buf); + b->vb2_buf.timestamp = ktime_get_ns(); + user_sequence = ctx_buf->user_sequence; + mtk_fd_ctx_buffer_done(ctx_buf, ctxf_state); + mtk_fd_v4l2_buffer_done(&b->vb2_buf, vbf_state); + } + } + + mtk_fd_ctx_free_frame(&fd_dev->ctx, frame); + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_core_job_finish); + +int mtk_fd_ctx_finish_frame(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *frame_bundle, + int done) +{ + spin_lock(&dev_ctx->qlock); + frame_bundle->state = MTK_FD_CTX_FRAME_PROCESSING; + list_add_tail(&frame_bundle->list, &dev_ctx->processing_frames.list); + spin_unlock(&dev_ctx->qlock); + return 0; +} + +static void set_img_fmt(struct v4l2_pix_format_mplane *mfmt_to_fill, + struct mtk_fd_ctx_format *ctx_fmt) +{ + int i = 0; + + mfmt_to_fill->pixelformat = ctx_fmt->fmt.img.pixelformat; + mfmt_to_fill->num_planes = ctx_fmt->fmt.img.num_planes; + + pr_info("%s: Fmt(%d),w(%d),h(%d)\n", + __func__, + mfmt_to_fill->pixelformat, + mfmt_to_fill->width, + mfmt_to_fill->height); + + /** + * The implementation wil be adjust after integrating MDP module + * since it provides the common format suppporting function + */ + for (i = 0 ; i < mfmt_to_fill->num_planes; ++i) { + int bpl = (mfmt_to_fill->width * + ctx_fmt->fmt.img.row_depth[i]) / 8; + int sizeimage = (mfmt_to_fill->width * mfmt_to_fill->height * + ctx_fmt->fmt.img.depth[i]) / 8; + + mfmt_to_fill->plane_fmt[i].bytesperline = bpl; + mfmt_to_fill->plane_fmt[i].sizeimage = sizeimage; + pr_info("plane(%d):bpl(%d),sizeimage(%u)\n", + i, bpl, mfmt_to_fill->plane_fmt[i].sizeimage); + } +} + +static void set_meta_fmt(struct v4l2_meta_format *metafmt_to_fill, + struct mtk_fd_ctx_format *ctx_fmt) +{ + metafmt_to_fill->dataformat = ctx_fmt->fmt.meta.dataformat; + + if (ctx_fmt->fmt.meta.max_buffer_size <= 0 || + ctx_fmt->fmt.meta.max_buffer_size > + MTK_FD_CTX_META_BUF_DEFAULT_SIZE) { + pr_warn("buf size of meta(%u) can't be 0, use default %u\n", + ctx_fmt->fmt.meta.dataformat, + MTK_FD_CTX_META_BUF_DEFAULT_SIZE); + metafmt_to_fill->buffersize = MTK_FD_CTX_META_BUF_DEFAULT_SIZE; + } else { + pr_info("Load the meta size setting %u\n", + ctx_fmt->fmt.meta.max_buffer_size); + metafmt_to_fill->buffersize = ctx_fmt->fmt.meta.max_buffer_size; + } +} + +/* Get the default format setting */ +int mtk_fd_ctx_format_load_default_fmt(struct mtk_fd_ctx_queue *queue, + struct v4l2_format *fmt_to_fill) +{ + struct mtk_fd_ctx_format *ctx_fmt = NULL; + + if (queue->desc.num_fmts == 0) + return 0; /* no format support list associated to this queue */ + + if (queue->desc.default_fmt_idx >= queue->desc.num_fmts) { + pr_warn("Queue(%s) err: default idx(%d) must < num_fmts(%d)\n", + queue->desc.name, queue->desc.default_fmt_idx, + queue->desc.num_fmts); + queue->desc.default_fmt_idx = 0; + pr_warn("Queue(%s) : reset default idx(%d)\n", + queue->desc.name, queue->desc.default_fmt_idx); + } + + ctx_fmt = &queue->desc.fmts[queue->desc.default_fmt_idx]; + + /* Check the type of the buffer */ + if (queue->desc.image) { + struct v4l2_pix_format_mplane *node_fmt = + &fmt_to_fill->fmt.pix_mp; + + if (queue->desc.capture) { + fmt_to_fill->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + node_fmt->width = MTK_FD_OUTPUT_MAX_WIDTH; + node_fmt->height = MTK_FD_OUTPUT_MAX_HEIGHT; + } else { + fmt_to_fill->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + node_fmt->width = MTK_FD_INPUT_MAX_WIDTH; + node_fmt->height = MTK_FD_INPUT_MAX_HEIGHT; + } + set_img_fmt(node_fmt, ctx_fmt); + } else { + /* meta buffer type */ + struct v4l2_meta_format *node_fmt = &fmt_to_fill->fmt.meta; + + if (queue->desc.capture) + fmt_to_fill->type = V4L2_BUF_TYPE_META_CAPTURE; + else + fmt_to_fill->type = V4L2_BUF_TYPE_META_OUTPUT; + + set_meta_fmt(node_fmt, ctx_fmt); + } + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_format_load_default_fmt); + +static struct mtk_fd_ctx_format *mtk_fd_ctx_find_fmt + (struct mtk_fd_ctx_queue *queue, u32 format) +{ + int i; + struct mtk_fd_ctx_format *ctx_fmt; + + for (i = 0; i < queue->desc.num_fmts; i++) { + ctx_fmt = &queue->desc.fmts[i]; + if (queue->desc.image) { + pr_debug("idx(%d), pixelformat(%x), fmt(%x)\n", + i, ctx_fmt->fmt.img.pixelformat, format); + if (ctx_fmt->fmt.img.pixelformat == format) + return ctx_fmt; + } else { + if (ctx_fmt->fmt.meta.dataformat == format) + return ctx_fmt; + } + } + return NULL; +} + +int mtk_fd_ctx_fmt_set_meta(struct mtk_fd_ctx *dev_ctx, int queue_id, + struct v4l2_meta_format *user_fmt, + struct v4l2_meta_format *node_fmt) +{ + struct mtk_fd_ctx_queue *queue = NULL; + struct mtk_fd_ctx_format *ctx_fmt; + + if (queue_id >= dev_ctx->queues_attr.total_num) { + pr_err("Invalid queue id:%d\n", queue_id); + return -EINVAL; + } + + queue = &dev_ctx->queue[queue_id]; + if (!user_fmt || !node_fmt) + return -EINVAL; + + ctx_fmt = mtk_fd_ctx_find_fmt(queue, user_fmt->dataformat); + if (!ctx_fmt) + return -EINVAL; + + queue->ctx_fmt = ctx_fmt; + set_meta_fmt(node_fmt, ctx_fmt); + *user_fmt = *node_fmt; + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_fmt_set_meta); + +int mtk_fd_ctx_fmt_set_img(struct mtk_fd_ctx *dev_ctx, int queue_id, + struct v4l2_pix_format_mplane *user_fmt, + struct v4l2_pix_format_mplane *node_fmt) +{ + struct mtk_fd_ctx_queue *queue = NULL; + struct mtk_fd_ctx_format *ctx_fmt; + + if (queue_id >= dev_ctx->queues_attr.total_num) { + pr_err("Invalid queue id:%d\n", queue_id); + return -EINVAL; + } + + queue = &dev_ctx->queue[queue_id]; + if (!user_fmt || !node_fmt) + return -EINVAL; + + ctx_fmt = mtk_fd_ctx_find_fmt(queue, user_fmt->pixelformat); + if (!ctx_fmt) + return -EINVAL; + + queue->ctx_fmt = ctx_fmt; + node_fmt->width = user_fmt->width; + node_fmt->height = user_fmt->height; + + set_img_fmt(node_fmt, ctx_fmt); + + *user_fmt = *node_fmt; + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_fmt_set_img); + +int mtk_fd_ctx_streamon(struct mtk_fd_ctx *dev_ctx) +{ + int ret = 0; + + if (dev_ctx->streaming) { + pr_warn("stream on failed, pdev(%llx), ctx(%d) already stream on\n", + (long long)dev_ctx->pdev, dev_ctx->ctx_id); + return -EBUSY; + } + + ret = mtk_fd_streamon(dev_ctx->pdev, dev_ctx->ctx_id); + if (ret) { + pr_err("streamon: ctx(%d) failed, notified by context\n", + dev_ctx->ctx_id); + return -EBUSY; + } + + dev_ctx->streaming = true; + ret = mtk_fd_dev_queue_buffers(mtk_fd_ctx_to_dev(dev_ctx), true); + if (ret) + pr_err("failed to queue initial buffers (%d)", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_streamon); + +int mtk_fd_ctx_streamoff(struct mtk_fd_ctx *dev_ctx) +{ + int ret = 0; + + if (!dev_ctx->streaming) { + pr_warn("Do nothing, pdev(%llx), ctx(%d) is already stream off\n", + (long long)dev_ctx->pdev, dev_ctx->ctx_id); + return -EBUSY; + } + + ret = mtk_fd_streamoff(dev_ctx->pdev, dev_ctx->ctx_id); + if (ret) { + pr_warn("streamoff: ctx(%d) failed, notified by context\n", + dev_ctx->ctx_id); + return -EBUSY; + } + + dev_ctx->streaming = false; + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_streamoff); + +int mtk_fd_ctx_init_frame_bundles(struct mtk_fd_ctx *dev_ctx) +{ + int i = 0; + + dev_ctx->num_frame_bundle = VB2_MAX_FRAME; + + spin_lock(&dev_ctx->qlock); + + /* Reset the queue*/ + INIT_LIST_HEAD(&dev_ctx->processing_frames.list); + INIT_LIST_HEAD(&dev_ctx->free_frames.list); + + for (i = 0; i < dev_ctx->num_frame_bundle; i++) { + struct mtk_fd_ctx_frame_bundle *frame_bundle = + &dev_ctx->frame_bundles[i]; + frame_bundle->state = MTK_FD_CTX_FRAME_NEW; + list_add_tail(&frame_bundle->list, &dev_ctx->free_frames.list); + } + + spin_unlock(&dev_ctx->qlock); + + return 0; +} + +int mtk_fd_ctx_open(struct mtk_fd_ctx *dev_ctx) +{ + struct mtk_fd_dev *fd_dev = mtk_fd_ctx_to_dev(dev_ctx); + unsigned int enabled_dma_ports = 0; + int i = 0; + + if (!dev_ctx) + return -EINVAL; + + /* Get the enabled DMA ports */ + for (i = 0; i < fd_dev->mem2mem2.num_nodes; i++) { + if (fd_dev->mem2mem2.nodes[i].enabled) + enabled_dma_ports |= dev_ctx->queue[i].desc.dma_port; + } + + dev_ctx->enabled_dma_ports = enabled_dma_ports; + + dev_dbg(&fd_dev->pdev->dev, "open device: (%llx)\n", + (long long)&fd_dev->pdev->dev); + + /* Workaround for SCP EMI access */ + mtk_fd_smem_enable_mpu(&dev_ctx->smem_device->dev); + + /* Init the frame bundle pool */ + mtk_fd_ctx_init_frame_bundles(dev_ctx); + + return mtk_fd_open(dev_ctx->pdev); +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_open); + +int mtk_fd_ctx_release(struct mtk_fd_ctx *dev_ctx) +{ + return mtk_fd_release(dev_ctx->pdev); +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_release); + +static int mtk_fd_ctx_core_job_start(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *bundle) +{ + struct platform_device *pdev = dev_ctx->pdev; + int ret = 0; + struct v4l2_fd_param fd_param; + struct mtk_fd_ctx_buffer *ctx_buf_yuv_in = NULL; + struct mtk_fd_ctx_buffer *ctx_buf_meta_in = NULL; + struct mtk_fd_ctx_buffer *ctx_buf_meta_out = NULL; + + if (!pdev || !bundle) { + dev_err(&pdev->dev, + "pdev(%llx) and param(%llx) in start can't be NULL\n", + (long long)pdev, (long long)bundle); + return -EINVAL; + } + memset(&fd_param, 0, sizeof(struct v4l2_fd_param)); + fd_param.frame_id = bundle->id; + + /* Img-in buffer */ + ctx_buf_yuv_in = bundle->buffers[MTK_FD_CTX_FD_YUV_IN]; + if (ctx_buf_yuv_in) { + fd_param.src_img.iova = (uint32_t)ctx_buf_yuv_in->daddr; + fd_param.src_img.va = (uint64_t)ctx_buf_yuv_in->vaddr; + fd_param.src_img_h = + (uint16_t)ctx_buf_yuv_in->fmt.pix_mp.height; + fd_param.src_img_w = + (uint16_t)ctx_buf_yuv_in->fmt.pix_mp.width; + } + + /* Meta-in buffer */ + ctx_buf_meta_in = bundle->buffers[MTK_FD_CTX_FD_CONFIG_IN]; + if (ctx_buf_meta_in) { + fd_param.fd_user_param.va = (uint64_t)ctx_buf_meta_in->vaddr; + fd_param.fd_user_param.pa = (uint32_t)ctx_buf_meta_in->paddr; + fd_param.fd_user_param.iova = (uint32_t)ctx_buf_meta_in->daddr; + } else { + dev_err(&pdev->dev, "meta in is null!\n"); + fd_param.fd_user_param.pa = 0; + fd_param.fd_user_param.va = 0; + fd_param.fd_user_param.iova = 0; + } + + /* Meta-out buffer */ + ctx_buf_meta_out = bundle->buffers[MTK_FD_CTX_FD_OUT]; + if (ctx_buf_meta_out) { + fd_param.fd_user_result.va = (uint64_t)ctx_buf_meta_out->vaddr; + fd_param.fd_user_result.pa = (uint32_t)ctx_buf_meta_out->paddr; + fd_param.fd_user_result.iova = + (uint32_t)ctx_buf_meta_out->daddr; + } else { + dev_err(&pdev->dev, "meta out is null!\n"); + fd_param.fd_user_result.pa = 0; + fd_param.fd_user_result.va = 0; + fd_param.fd_user_result.iova = 0; + } + + dev_info(&pdev->dev, + "Delegate job to mtk_fd_enqueue: pdev(0x%llx), frame(%d)\n", + (long long)pdev, bundle->id); + ret = mtk_fd_enqueue(pdev, &fd_param); + + if (ret) { + dev_warn(&pdev->dev, "mtk_fd_enqueue failed: %d\n", ret); + return -EBUSY; + } + + return 0; +} + +static void debug_bundle(struct mtk_fd_ctx_frame_bundle *bundle_data) +{ + int i = 0; + + if (!bundle_data) { + pr_warn("bundle_data is NULL\n"); + return; + } + + pr_debug("bundle buf nums (%d, %d,%d,%d)\n", + bundle_data->num_img_capture_bufs, + bundle_data->num_img_output_bufs, + bundle_data->num_meta_capture_bufs, + bundle_data->num_meta_output_bufs); + + for (i = 0; i < 16 ; i++) { + pr_debug("Bundle, buf[%d] = %llx\n", + i, + (unsigned long long)bundle_data->buffers[i]); + } + + pr_debug("Bundle last idx: %d\n", bundle_data->last_index); +} + +static struct mtk_fd_ctx_frame_bundle *mtk_fd_ctx_get_free_frame + (struct mtk_fd_ctx *dev_ctx) +{ + struct mtk_fd_ctx_frame_bundle *frame_bundle = NULL; + + spin_lock(&dev_ctx->qlock); + list_for_each_entry(frame_bundle, + &dev_ctx->free_frames.list, list){ + pr_debug("Check frame: state %d, new should be %d\n", + frame_bundle->state, MTK_FD_CTX_FRAME_NEW); + if (frame_bundle->state == MTK_FD_CTX_FRAME_NEW) { + frame_bundle->state = MTK_FD_CTX_FRAME_PREPARED; + pr_debug("Found free frame\n"); + spin_unlock(&dev_ctx->qlock); + return frame_bundle; + } + } + spin_unlock(&dev_ctx->qlock); + pr_err("Can't found any bundle is MTK_FD_CTX_FRAME_NEW\n"); + return NULL; +} + +static int mtk_fd_ctx_process_frame + (struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *frame_bundle) +{ + spin_lock(&dev_ctx->qlock); + + frame_bundle->state = MTK_FD_CTX_FRAME_PROCESSING; + list_del(&frame_bundle->list); + list_add_tail(&frame_bundle->list, &dev_ctx->processing_frames.list); + + spin_unlock(&dev_ctx->qlock); + return 0; +} + +int mtk_fd_ctx_trigger_job(struct mtk_fd_ctx *dev_ctx, + struct mtk_fd_ctx_frame_bundle *bundle_data) +{ + /* Scan all buffers and filled the ipi frame data*/ + int i = 0; + struct mtk_fd_ctx_finish_param fram_param; + + struct mtk_fd_ctx_frame_bundle *bundle = + mtk_fd_ctx_get_free_frame(dev_ctx); + + pr_debug("Bundle data: , ctx id:%d\n", + dev_ctx->ctx_id); + + debug_bundle(bundle_data); + + if (!bundle) { + pr_err("bundle can't be NULL\n"); + goto FAILE_JOB_NOT_TRIGGER; + } + if (!bundle_data) { + pr_err("bundle_data can't be NULL\n"); + goto FAILE_JOB_NOT_TRIGGER; + } + + pr_debug("Copy bundle_data->buffers to bundle->buffers\n"); + memcpy(bundle->buffers, bundle_data->buffers, + sizeof(struct mtk_fd_ctx_buffer *) * + MTK_FD_CTX_FRAME_BUNDLE_BUFFER_MAX); + + pr_debug("bundle setup (%d, %d,%d,%d)\n", + bundle_data->num_img_capture_bufs, + bundle_data->num_img_output_bufs, + bundle_data->num_meta_capture_bufs, + bundle_data->num_meta_output_bufs); + + bundle->num_img_capture_bufs = bundle_data->num_img_capture_bufs; + bundle->num_img_output_bufs = bundle_data->num_img_output_bufs; + bundle->num_meta_capture_bufs = bundle_data->num_meta_capture_bufs; + bundle->num_meta_output_bufs = bundle_data->num_meta_output_bufs; + bundle->id = mtk_fd_ctx_next_global_frame_sequence(dev_ctx, + dev_ctx->ctx_id); + bundle->last_index = dev_ctx->queues_attr.total_num - 1; + + debug_bundle(bundle); + + pr_debug("Fill Address data\n"); + for (i = 0; i <= bundle->last_index; i++) { + struct mtk_fd_ctx_buffer *ctx_buf = bundle->buffers[i]; + struct vb2_v4l2_buffer *b = NULL; + + pr_debug("Process queue[%d], ctx_buf:(%llx)\n", + i, (unsigned long long)ctx_buf); + + if (!ctx_buf) { + pr_warn("queue[%d], ctx_buf is NULL!!\n", i); + continue; + } + + pr_debug("Get VB2 V4L2 buffer\n"); + b = mtk_fd_ctx_buffer_get_vb2_v4l2_buffer(ctx_buf); + + ctx_buf->image = dev_ctx->queue[ctx_buf->queue].desc.image; + ctx_buf->capture = dev_ctx->queue[ctx_buf->queue].desc.capture; + /* copy the fmt setting for queue's fmt*/ + ctx_buf->fmt = dev_ctx->queue[ctx_buf->queue].fmt; + ctx_buf->ctx_fmt = dev_ctx->queue[ctx_buf->queue].ctx_fmt; + ctx_buf->frame_id = bundle->id; + ctx_buf->daddr = + vb2_dma_contig_plane_dma_addr(&b->vb2_buf, 0); + pr_debug("%s:vb2_buf: type(%d),idx(%d),mem(%d)\n", + __func__, + b->vb2_buf.type, + b->vb2_buf.index, + b->vb2_buf.memory); + ctx_buf->vaddr = vb2_plane_vaddr(&b->vb2_buf, 0); + ctx_buf->buffer_usage = dev_ctx->queue[i].buffer_usage; + ctx_buf->rotation = dev_ctx->queue[i].rotation; + pr_debug("Buf: queue(%d), vaddr(%llx), daddr(%llx)", + ctx_buf->queue, (unsigned long long)ctx_buf->vaddr, + (unsigned long long)ctx_buf->daddr); + + if (!ctx_buf->image) { + ctx_buf->paddr = + mtk_fd_smem_iova_to_phys + (&dev_ctx->smem_device->dev, + ctx_buf->daddr); + } else { + pr_debug("No pa: it is a image buffer\n"); + ctx_buf->paddr = 0; + } + ctx_buf->state = MTK_FD_CTX_BUFFER_PROCESSING; + } + + if (mtk_fd_ctx_process_frame(dev_ctx, bundle)) { + pr_err("mtk_fd_ctx_process_frame failed: frame(%d)\n", + bundle->id); + goto FAILE_JOB_NOT_TRIGGER; + } + + if (dev_ctx->mode == MTK_FD_CTX_MODE_DEBUG_BYPASS_JOB_TRIGGER) { + memset(&fram_param, 0, sizeof(struct mtk_fd_ctx_finish_param)); + fram_param.frame_id = bundle->id; + fram_param.state = MTK_FD_CTX_FRAME_DATA_DONE; + pr_debug("Ctx(%d) in HW bypass mode, will not trigger hw\n", + dev_ctx->ctx_id); + + mtk_fd_ctx_core_job_finish(dev_ctx, (void *)&fram_param); + return 0; + } + + if (mtk_fd_ctx_core_job_start(dev_ctx, bundle)) + goto FAILE_JOB_NOT_TRIGGER; + return 0; + +FAILE_JOB_NOT_TRIGGER: + pr_debug("FAILE_JOB_NOT_TRIGGER: init fram_param: %llx\n", + (unsigned long long)&fram_param); + memset(&fram_param, 0, sizeof(struct mtk_fd_ctx_finish_param)); + fram_param.frame_id = bundle->id; + fram_param.state = MTK_FD_CTX_FRAME_DATA_ERROR; + pr_debug("Call mtk_fd_ctx_core_job_finish_cb: fram_param: %llx", + (unsigned long long)&fram_param); + mtk_fd_ctx_core_job_finish(dev_ctx, (void *)&fram_param); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_trigger_job); diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.c b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.c new file mode 100644 index 0000000..7e3acf7 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Mediatek Corporation. + * Copyright (c) 2017 Intel Corporation. + * Copyright (C) 2017 Google, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License 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. + * + * MTK_FD-dev is highly based on Intel IPU 3 chrome driver + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <media/videobuf2-dma-contig.h> +#include "mtk_fd-dev.h" + +static struct platform_device *mtk_fd_dev_of_find_smem_dev + (struct platform_device *pdev); + +/* Initliaze a mtk_fd_dev representing a completed HW FD */ +/* device */ +int mtk_fd_dev_init(struct mtk_fd_dev *fd_dev, + struct platform_device *pdev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev) +{ + int r = 0; + + fd_dev->pdev = pdev; + + mutex_init(&fd_dev->lock); + atomic_set(&fd_dev->qbuf_barrier, 0); + init_waitqueue_head(&fd_dev->buf_drain_wq); + + /* v4l2 sub-device registration */ + r = mtk_fd_dev_mem2mem2_init(fd_dev, media_dev, v4l2_dev); + + if (r) { + dev_err(&fd_dev->pdev->dev, + "failed to create V4L2 devices (%d)\n", r); + goto failed_mem2mem2; + } + + return 0; + +failed_mem2mem2: + mutex_destroy(&fd_dev->lock); + return r; +} + +int mtk_fd_dev_get_total_node(struct mtk_fd_dev *mtk_fd_dev) +{ + return mtk_fd_dev->ctx.queues_attr.total_num; +} + +int mtk_fd_dev_mem2mem2_init(struct mtk_fd_dev *fd_dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev) +{ + int r, i; + const int queue_master = fd_dev->ctx.queues_attr.master; + + pr_info("mem2mem2.name: %s\n", fd_dev->ctx.device_name); + fd_dev->mem2mem2.name = fd_dev->ctx.device_name; + fd_dev->mem2mem2.model = fd_dev->ctx.device_name; + fd_dev->mem2mem2.num_nodes = + mtk_fd_dev_get_total_node(fd_dev); + fd_dev->mem2mem2.vb2_mem_ops = &vb2_dma_contig_memops; + fd_dev->mem2mem2.buf_struct_size = + sizeof(struct mtk_fd_dev_buffer); + + fd_dev->mem2mem2.nodes = fd_dev->mem2mem2_nodes; + fd_dev->mem2mem2.dev = &fd_dev->pdev->dev; + + for (i = 0; i < fd_dev->ctx.dev_node_num; i++) { + fd_dev->mem2mem2.nodes[i].name = + mtk_fd_dev_get_node_name(fd_dev, i); + fd_dev->mem2mem2.nodes[i].output = + (!fd_dev->ctx.queue[i].desc.capture); + fd_dev->mem2mem2.nodes[i].immutable = false; + fd_dev->mem2mem2.nodes[i].enabled = false; + atomic_set(&fd_dev->mem2mem2.nodes[i].sequence, 0); + } + + /* Master queue is always enabled */ + fd_dev->mem2mem2.nodes[queue_master].immutable = true; + fd_dev->mem2mem2.nodes[queue_master].enabled = true; + + pr_info("register v4l2 for %llx\n", + (unsigned long long)fd_dev); + r = mtk_fd_mem2mem2_v4l2_register(fd_dev, media_dev, v4l2_dev); + + if (r) { + pr_err("v4l2 init failed, dev(ctx:%d)\n", fd_dev->ctx.ctx_id); + return r; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_mem2mem2_init); + +void mtk_fd_dev_mem2mem2_exit(struct mtk_fd_dev *fd_dev) +{ + mtk_fd_v4l2_unregister(fd_dev); +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_mem2mem2_exit); + +char *mtk_fd_dev_get_node_name + (struct mtk_fd_dev *fd_dev, int node) +{ + struct mtk_fd_ctx_queue_desc *mapped_queue_desc = + &fd_dev->ctx.queue[node].desc; + + return mapped_queue_desc->name; +} + +/* Get a free buffer from a video node */ +static struct mtk_fd_ctx_buffer __maybe_unused *mtk_fd_dev_queue_getbuf + (struct mtk_fd_dev *fd_dev, int node) +{ + struct mtk_fd_dev_buffer *buf; + int queue = -1; + + if (node > fd_dev->ctx.dev_node_num || node < 0) { + dev_err(&fd_dev->pdev->dev, "Invalid mtk_fd_dev node.\n"); + return NULL; + } + + /* Get the corrosponding queue id of the video node */ + /* Currently the queue id is the same as the node number */ + queue = node; + + if (queue < 0) { + dev_err(&fd_dev->pdev->dev, "Invalid mtk_fd_dev node.\n"); + return NULL; + } + + /* Find first free buffer from the node */ + list_for_each_entry(buf, &fd_dev->mem2mem2.nodes[node].buffers, + m2m2_buf.list) { + if (mtk_fd_ctx_get_buffer_state(&buf->ctx_buf) + == MTK_FD_CTX_BUFFER_NEW) + return &buf->ctx_buf; + } + + /* There were no free buffers*/ + return NULL; +} + +int mtk_fd_dev_get_queue_id_of_dev_node(struct mtk_fd_dev *fd_dev, + struct mtk_fd_dev_video_device *node) +{ + return (node - fd_dev->mem2mem2.nodes); +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_get_queue_id_of_dev_node); + +int mtk_fd_dev_queue_buffers(struct mtk_fd_dev *fd_dev, + bool initial) +{ + unsigned int node; + int r = 0; + struct mtk_fd_dev_buffer *ibuf; + struct mtk_fd_ctx_frame_bundle bundle; + const int mtk_fd_dev_node_num = mtk_fd_dev_get_total_node(fd_dev); + const int queue_master = fd_dev->ctx.queues_attr.master; + + memset(&bundle, 0, sizeof(struct mtk_fd_ctx_frame_bundle)); + + pr_info("%s, init(%d)\n", __func__, initial); + + if (!mtk_fd_ctx_is_streaming(&fd_dev->ctx)) { + pr_info("%s: stream off, no hw enqueue triggered\n", __func__); + return 0; + } + + mutex_lock(&fd_dev->lock); + + /* Buffer set is queued to background driver (e.g. DIP, FD, and P1) */ + /* only when master input buffer is ready */ + if (!mtk_fd_dev_queue_getbuf(fd_dev, queue_master)) { + mutex_unlock(&fd_dev->lock); + return 0; + } + + /* Check all node from the node after the master node */ + for (node = (queue_master + 1) % mtk_fd_dev_node_num; + 1; node = (node + 1) % mtk_fd_dev_node_num) { + pr_info("Check node(%d), queue enabled(%d), node enabled(%d)\n", + node, fd_dev->queue_enabled[node], + fd_dev->mem2mem2.nodes[node].enabled); + + /* May skip some node according the scenario in the future */ + if (fd_dev->queue_enabled[node] || + fd_dev->mem2mem2.nodes[node].enabled) { + struct mtk_fd_ctx_buffer *buf = + mtk_fd_dev_queue_getbuf(fd_dev, node); + char *node_name = + mtk_fd_dev_get_node_name(fd_dev, node); + + if (!buf) { + dev_dbg(&fd_dev->pdev->dev, + "No free buffer of enabled node %s\n", + node_name); + break; + } + + /* To show the debug message */ + ibuf = container_of(buf, + struct mtk_fd_dev_buffer, ctx_buf); + dev_dbg(&fd_dev->pdev->dev, + "may queue user %s buffer idx(%d) to ctx\n", + node_name, + ibuf->m2m2_buf.vbb.vb2_buf.index); + mtk_fd_ctx_frame_bundle_add(&fd_dev->ctx, + &bundle, buf); + } + + /* Stop if there is no free buffer in master input node */ + if (node == queue_master) { + if (mtk_fd_dev_queue_getbuf(fd_dev, queue_master)) { + /* Has collected all buffer required */ + mtk_fd_ctx_trigger_job(&fd_dev->ctx, &bundle); + } else { + pr_debug("no new buffer found in master node, not trigger job\n"); + break; + } + } + } + mutex_unlock(&fd_dev->lock); + + if (r && r != -EBUSY) + goto failed; + + return 0; + +failed: + /* + * On error, mark all buffers as failed which are not + * yet queued to CSS + */ + dev_err(&fd_dev->pdev->dev, + "failed to queue buffer to ctx on queue %i (%d)\n", + node, r); + + if (initial) + /* If we were called from streamon(), no need to finish bufs */ + return r; + + for (node = 0; node < mtk_fd_dev_node_num; node++) { + struct mtk_fd_dev_buffer *buf, *buf0; + + if (!fd_dev->queue_enabled[node]) + continue; /* Skip disabled queues */ + + mutex_lock(&fd_dev->lock); + list_for_each_entry_safe + (buf, buf0, + &fd_dev->mem2mem2.nodes[node].buffers, + m2m2_buf.list) { + if (mtk_fd_ctx_get_buffer_state(&buf->ctx_buf) == + MTK_FD_CTX_BUFFER_PROCESSING) + continue; /* Was already queued, skip */ + + mtk_fd_v4l2_buffer_done(&buf->m2m2_buf.vbb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + mutex_unlock(&fd_dev->lock); + } + + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_queue_buffers); + +int mtk_fd_dev_core_init(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev, + struct mtk_fd_ctx_desc *ctx_desc) +{ + return mtk_fd_dev_core_init_ext(pdev, + fd_dev, ctx_desc, NULL, NULL); +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_core_init); + +int mtk_fd_dev_core_init_ext(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev, + struct mtk_fd_ctx_desc *ctx_desc, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev) +{ + int r; + struct platform_device *smem_dev = NULL; + + smem_dev = mtk_fd_dev_of_find_smem_dev(pdev); + + if (!smem_dev) + dev_err(&pdev->dev, "failed to find smem_dev\n"); + + /* Device context must be initialized before device instance */ + r = mtk_fd_ctx_core_init(&fd_dev->ctx, pdev, + 0, ctx_desc, pdev, smem_dev); + + dev_info(&pdev->dev, "init fd_dev: %llx\n", + (unsigned long long)fd_dev); + /* init other device level members */ + mtk_fd_dev_init(fd_dev, pdev, media_dev, v4l2_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_core_init_ext); + +int mtk_fd_dev_core_release(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev) +{ + mtk_fd_dev_mem2mem2_exit(fd_dev); + mtk_fd_ctx_core_exit(&fd_dev->ctx); + mutex_destroy(&fd_dev->lock); + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_dev_core_release); + +static struct platform_device *mtk_fd_dev_of_find_smem_dev + (struct platform_device *pdev) +{ + struct device_node *smem_dev_node = NULL; + + if (!pdev) { + pr_err("Find_smem_dev failed, pdev can't be NULL\n"); + return NULL; + } + + smem_dev_node = of_parse_phandle(pdev->dev.of_node, + "smem_device", 0); + + if (!smem_dev_node) { + dev_err(&pdev->dev, + "failed to find isp smem device for (%s)\n", + pdev->name); + return NULL; + } + + dev_dbg(&pdev->dev, "smem of node found, try to discovery device\n"); + return of_find_device_by_node(smem_dev_node); +} + diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.h b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.h new file mode 100644 index 0000000..d2b7d77 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-dev.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2018 Mediatek Corporation. + * Copyright (c) 2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License 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. + * + * MTK_FD-dev is highly based on Intel IPU 3 chrome driver + * + */ + +#ifndef __MTK_FD_DEV_H__ +#define __MTK_FD_DEV_H__ + +#include <linux/platform_device.h> +#include <linux/version.h> +#include <media/v4l2-device.h> +#include <media/videobuf2-v4l2.h> +#include "mtk_fd-ctx.h" + +/* Added the macro for early stage verification */ +/* based on kernel 4.4 environment. */ +/* I will remove the version check after getting */ +/* the devlopment platform based on 4.14 */ +#define MTK_FD_KERNEL_BASE_VERSION KERNEL_VERSION(4, 14, 0) + +#define MTK_FD_DEV_NODE_MAX (MTK_FD_CTX_QUEUES) + +#define MTK_FD_INPUT_MIN_WIDTH 0U +#define MTK_FD_INPUT_MIN_HEIGHT 0U +#define MTK_FD_INPUT_MAX_WIDTH 480U +#define MTK_FD_INPUT_MAX_HEIGHT 640U +#define MTK_FD_OUTPUT_MIN_WIDTH 2U +#define MTK_FD_OUTPUT_MIN_HEIGHT 2U +#define MTK_FD_OUTPUT_MAX_WIDTH 480U +#define MTK_FD_OUTPUT_MAX_HEIGHT 640U + +#define file_to_mtk_fd_node(__file) \ + container_of(video_devdata(__file),\ + struct mtk_fd_dev_video_device, vdev) + +#define mtk_fd_ctx_to_dev(__ctx) \ + container_of(__ctx,\ + struct mtk_fd_dev, ctx) + +#define mtk_fd_m2m_to_dev(__m2m) \ + container_of(__m2m,\ + struct mtk_fd_dev, mem2mem2) + +#define mtk_fd_subdev_to_dev(__sd) \ + container_of(__sd, \ + struct mtk_fd_dev, mem2mem2.subdev) + +#define mtk_fd_vbq_to_isp_node(__vq) \ + container_of(__vq, \ + struct mtk_fd_dev_video_device, vbq) + +#define mtk_fd_ctx_buf_to_dev_buf(__ctx_buf) \ + container_of(__ctx_buf, \ + struct mtk_fd_dev_buffer, ctx_buf) + +#define mtk_fd_vb2_buf_to_dev_buf(__vb) \ + container_of(vb, \ + struct mtk_fd_dev_buffer, \ + m2m2_buf.vbb.vb2_buf) + +#define mtk_fd_vb2_buf_to_m2m_buf(__vb) \ + container_of(__vb, \ + struct mtk_fd_mem2mem2_buffer, \ + vbb.vb2_buf) + +#define mtk_fd_subdev_to_m2m(__sd) \ + container_of(__sd, \ + struct mtk_fd_mem2mem2_device, subdev) + +struct mtk_fd_mem2mem2_device; + +struct mtk_fd_mem2mem2_buffer { + struct vb2_v4l2_buffer vbb; + struct list_head list; +}; + +struct mtk_fd_dev_buffer { + struct mtk_fd_mem2mem2_buffer m2m2_buf; + /* Intenal part */ + struct mtk_fd_ctx_buffer ctx_buf; +}; + +struct mtk_fd_dev_video_device { + const char *name; + int output; + int immutable; + int enabled; + int queued; + struct v4l2_format vdev_fmt; + struct video_device vdev; + struct media_pad vdev_pad; + struct v4l2_mbus_framefmt pad_fmt; + struct vb2_queue vbq; + struct list_head buffers; + struct mutex lock; /* Protect node data */ + atomic_t sequence; +}; + +struct mtk_fd_mem2mem2_device { + const char *name; + const char *model; + struct device *dev; + int num_nodes; + struct mtk_fd_dev_video_device *nodes; + const struct vb2_mem_ops *vb2_mem_ops; + unsigned int buf_struct_size; + int streaming; + struct v4l2_device *v4l2_dev; + struct media_device *media_dev; + struct media_pipeline pipeline; + struct v4l2_subdev subdev; + struct media_pad *subdev_pads; + struct v4l2_file_operations v4l2_file_ops; + const struct file_operations fops; +}; + +struct mtk_fd_dev { + struct platform_device *pdev; + struct mtk_fd_dev_video_device mem2mem2_nodes[MTK_FD_DEV_NODE_MAX]; + int queue_enabled[MTK_FD_DEV_NODE_MAX]; + struct mtk_fd_mem2mem2_device mem2mem2; + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct mtk_fd_ctx ctx; + struct mutex lock; /* queue protection */ + atomic_t qbuf_barrier; + struct { + struct v4l2_rect eff; + struct v4l2_rect bds; + struct v4l2_rect gdc; + } rect; + int suspend_in_stream; + wait_queue_head_t buf_drain_wq; +}; + +int mtk_fd_media_register(struct device *dev, + struct media_device *media_dev, + const char *model); + +int mtk_fd_v4l2_register(struct device *dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev); + +int mtk_fd_v4l2_unregister(struct mtk_fd_dev *dev); + +int mtk_fd_mem2mem2_v4l2_register(struct mtk_fd_dev *dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev); + +void mtk_fd_v4l2_buffer_done(struct vb2_buffer *vb, + enum vb2_buffer_state state); + +int mtk_fd_dev_queue_buffers(struct mtk_fd_dev *dev, bool initial); + +int mtk_fd_dev_get_total_node(struct mtk_fd_dev *mtk_fd_dev); + +char *mtk_fd_dev_get_node_name(struct mtk_fd_dev *mtk_fd_dev_obj, int node); + +int mtk_fd_dev_init(struct mtk_fd_dev *fd_dev, + struct platform_device *pdev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev); + +void mtk_fd_dev_mem2mem2_exit(struct mtk_fd_dev *mtk_fd_dev_obj); + +int mtk_fd_dev_mem2mem2_init(struct mtk_fd_dev *fd_dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev); + +int mtk_fd_dev_get_queue_id_of_dev_node(struct mtk_fd_dev *mtk_fd_dev_obj, + struct mtk_fd_dev_video_device *node); + +int mtk_fd_dev_core_init(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev, + struct mtk_fd_ctx_desc *ctx_desc); + +int mtk_fd_dev_core_init_ext(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev, + struct mtk_fd_ctx_desc *ctx_desc, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev); + +int mtk_fd_dev_core_release(struct platform_device *pdev, + struct mtk_fd_dev *fd_dev); + +#endif /* __MTK_FD_DEV_H__ */ diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-smem-drv.c b/drivers/media/platform/mtk-isp/fd/mtk_fd-smem-drv.c new file mode 100644 index 0000000..99a852d --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-smem-drv.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/of_reserved_mem.h> +#include <linux/dma-contiguous.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/iommu.h> +#include <asm/cacheflush.h> + +#define MTK_FD_SMEM_DEV_NAME "MTK-FD-SMEM" + +struct mtk_fd_smem_drv { + struct platform_device *pdev; + struct sg_table sgt; + struct page **smem_pages; + int num_smem_pages; + phys_addr_t smem_base; + dma_addr_t smem_dma_base; + int smem_size; +}; + +static struct reserved_mem *isp_reserved_smem; + +static int mtk_fd_smem_setup_dma_ops(struct device *smem_dev, + const struct dma_map_ops *smem_ops); + +static int mtk_fd_smem_get_sgtable(struct device *dev, + struct sg_table *sgt, + void *cpu_addr, dma_addr_t dma_addr, + size_t size, unsigned long attrs); + +static const struct dma_map_ops smem_dma_ops = { + .get_sgtable = mtk_fd_smem_get_sgtable, +}; + +static int mtk_fd_smem_init(struct mtk_fd_smem_drv **mtk_fd_smem_drv_out, + struct platform_device *pdev) +{ + struct mtk_fd_smem_drv *isp_sys = NULL; + struct device *dev = &pdev->dev; + + isp_sys = devm_kzalloc(dev, + sizeof(*isp_sys), GFP_KERNEL); + + isp_sys->pdev = pdev; + + *mtk_fd_smem_drv_out = isp_sys; + + return 0; +} + +static int mtk_fd_smem_drv_probe(struct platform_device *pdev) +{ + struct mtk_fd_smem_drv *smem_drv = NULL; + int r = 0; + struct device *dev = &pdev->dev; + + dev_dbg(dev, "probe mtk_fd_smem_drv\n"); + + r = mtk_fd_smem_init(&smem_drv, pdev); + + if (!smem_drv) + return -ENOMEM; + + dev_set_drvdata(dev, smem_drv); + + if (isp_reserved_smem) { + dma_addr_t dma_addr; + phys_addr_t addr; + struct iommu_domain *smem_dom; + int i = 0; + int size_align = 0; + struct page **pages = NULL; + int n_pages = 0; + struct sg_table *sgt = &smem_drv->sgt; + + size_align = round_down(isp_reserved_smem->size, + PAGE_SIZE); + n_pages = size_align >> PAGE_SHIFT; + + pages = kmalloc_array(n_pages, sizeof(struct page *), + GFP_KERNEL); + + if (!pages) + return -ENOMEM; + + for (i = 0; i < n_pages; i++) + pages[i] = phys_to_page(isp_reserved_smem->base + + i * PAGE_SIZE); + + r = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, + size_align, GFP_KERNEL); + + if (r) { + dev_err(dev, "failed to get alloca sg table\n"); + return -ENOMEM; + } + + dma_map_sg_attrs(dev, sgt->sgl, sgt->nents, + DMA_BIDIRECTIONAL, + DMA_ATTR_SKIP_CPU_SYNC); + + dma_addr = sg_dma_address(sgt->sgl); + smem_dom = iommu_get_domain_for_dev(dev); + addr = iommu_iova_to_phys(smem_dom, dma_addr); + + if (addr != isp_reserved_smem->base) + dev_err(dev, + "incorrect pa(%llx) from iommu_iova_to_phys, should be %llx\n", + (unsigned long long)addr, + (unsigned long long)isp_reserved_smem->base); + + r = dma_declare_coherent_memory(dev, + isp_reserved_smem->base, + dma_addr, size_align, DMA_MEMORY_EXCLUSIVE); + + dev_dbg(dev, + "Coherent mem base(%llx,%llx),size(%lx),ret(%d)\n", + isp_reserved_smem->base, + dma_addr, size_align, r); + + smem_drv->smem_base = isp_reserved_smem->base; + smem_drv->smem_size = size_align; + smem_drv->smem_pages = pages; + smem_drv->num_smem_pages = n_pages; + smem_drv->smem_dma_base = dma_addr; + + dev_dbg(dev, "smem_drv setting (%llx,%lx,%llx,%d)\n", + smem_drv->smem_base, smem_drv->smem_size, + (unsigned long long)smem_drv->smem_pages, + smem_drv->num_smem_pages); + } + + r = mtk_fd_smem_setup_dma_ops(dev, &smem_dma_ops); + + return r; +} + +phys_addr_t mtk_fd_smem_iova_to_phys(struct device *dev, + dma_addr_t iova) +{ + struct iommu_domain *smem_dom; + phys_addr_t addr; + phys_addr_t limit; + struct mtk_fd_smem_drv *smem_dev = + dev_get_drvdata(dev); + + if (!smem_dev) + return 0; + + smem_dom = iommu_get_domain_for_dev(dev); + + if (!smem_dom) + return 0; + + addr = iommu_iova_to_phys(smem_dom, iova); + + limit = smem_dev->smem_base + smem_dev->smem_size; + + if (addr < smem_dev->smem_base || addr >= limit) { + dev_err(dev, + "Unexpected paddr %pa (must >= %pa and <%pa)\n", + &addr, &smem_dev->smem_base, &limit); + return 0; + } + dev_dbg(dev, "Pa verifcation pass: %pa(>=%pa, <%pa)\n", + &addr, &smem_dev->smem_base, &limit); + return addr; +} + +static int mtk_fd_smem_drv_remove(struct platform_device *pdev) +{ + struct mtk_fd_smem_drv *smem_drv = + dev_get_drvdata(&pdev->dev); + + kfree(smem_drv->smem_pages); + return 0; +} + +static int mtk_fd_smem_drv_suspend(struct device *dev) +{ + return 0; +} + +static int mtk_fd_smem_drv_resume(struct device *dev) +{ + return 0; +} + +static int mtk_fd_smem_drv_dummy_cb(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops mtk_fd_smem_drv_pm_ops = { + SET_RUNTIME_PM_OPS(&mtk_fd_smem_drv_dummy_cb, + &mtk_fd_smem_drv_dummy_cb, NULL) + SET_SYSTEM_SLEEP_PM_OPS + (&mtk_fd_smem_drv_suspend, &mtk_fd_smem_drv_resume) +}; + +static const struct of_device_id mtk_fd_smem_drv_of_match[] = { + { + .compatible = "mediatek,fd_smem", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mtk_fd_smem_drv_of_match); + +static struct platform_driver mtk_fd_smem_driver = { + .probe = mtk_fd_smem_drv_probe, + .remove = mtk_fd_smem_drv_remove, + .driver = { + .name = MTK_FD_SMEM_DEV_NAME, + .of_match_table = + of_match_ptr(mtk_fd_smem_drv_of_match), + .pm = &mtk_fd_smem_drv_pm_ops, + }, +}; + +static int __init mtk_fd_smem_dma_setup(struct reserved_mem + *rmem) +{ + unsigned long node = rmem->fdt_node; + + if (of_get_flat_dt_prop(node, "reusable", NULL)) + return -EINVAL; + + if (!of_get_flat_dt_prop(node, "no-map", NULL)) { + pr_err("Reserved memory: regions without no-map are not yet supported\n"); + return -EINVAL; + } + + isp_reserved_smem = rmem; + + pr_debug("Reserved memory: created DMA memory pool at %pa, size %ld MiB\n", + &rmem->base, (unsigned long)rmem->size / SZ_1M); + return 0; +} + +RESERVEDMEM_OF_DECLARE(mtk_fd_smem, + "mediatek,reserve-memory-fd_smem", + mtk_fd_smem_dma_setup); + +int __init mtk_fd_smem_drv_init(void) +{ + int ret = 0; + + pr_debug("platform_driver_register: mtk_fd_smem_driver\n"); + ret = platform_driver_register(&mtk_fd_smem_driver); + + if (ret) + pr_warn("fd smem drv init failed, driver didn't probe\n"); + + return ret; +} +subsys_initcall(mtk_fd_smem_drv_init); + +void __exit mtk_fd_smem_drv_ext(void) +{ + platform_driver_unregister(&mtk_fd_smem_driver); +} +module_exit(mtk_fd_smem_drv_ext); + +/******************************************** + * MTK FD SMEM DMA ops * + ********************************************/ + +struct dma_coherent_mem { + void *virt_base; + dma_addr_t device_base; + unsigned long pfn_base; + int size; + int flags; + unsigned long *bitmap; + spinlock_t spinlock; /* protect the members in dma_coherent_mem */ + bool use_dev_dma_pfn_offset; +}; + +static struct dma_coherent_mem *dev_get_coherent_memory(struct device *dev) +{ + if (dev && dev->dma_mem) + return dev->dma_mem; + return NULL; +} + +static int mtk_fd_smem_get_sgtable(struct device *dev, + struct sg_table *sgt, + void *cpu_addr, dma_addr_t dma_addr, + size_t size, unsigned long attrs) +{ + struct mtk_fd_smem_drv *smem_dev = dev_get_drvdata(dev); + int n_pages_align = 0; + int size_align = 0; + int page_start = 0; + unsigned long long offset_p = 0; + unsigned long long offset_d = 0; + + phys_addr_t paddr = mtk_fd_smem_iova_to_phys(dev, dma_addr); + + offset_d = (unsigned long long)dma_addr - + (unsigned long long)smem_dev->smem_dma_base; + + offset_p = (unsigned long long)paddr - + (unsigned long long)smem_dev->smem_base; + + dev_dbg(dev, "%s:dma_addr:%llx,cpu_addr:%llx,pa:%llx,size:%d\n", + __func__, + (unsigned long long)dma_addr, + (unsigned long long)cpu_addr, + (unsigned long long)paddr, + size + ); + + dev_dbg(dev, "%s:offset p:%llx,offset d:%llx\n", + __func__, + (unsigned long long)offset_p, + (unsigned long long)offset_d + ); + + size_align = round_up(size, PAGE_SIZE); + n_pages_align = size_align >> PAGE_SHIFT; + page_start = offset_p >> PAGE_SHIFT; + + dev_dbg(dev, + "%s:page idx:%d,page pa:%llx,pa:%llx, aligned size:%d\n", + __func__, + page_start, + (unsigned long long)page_to_phys(*(smem_dev->smem_pages + + page_start)), + (unsigned long long)paddr, + size_align + ); + + if (!smem_dev) { + dev_err(dev, "can't get sgtable from smem_dev\n"); + return -EINVAL; + } + + dev_dbg(dev, "get sgt of the smem: %d pages\n", n_pages_align); + + return sg_alloc_table_from_pages(sgt, + smem_dev->smem_pages + page_start, + n_pages_align, + 0, size_align, GFP_KERNEL); +} + +static void *mtk_fd_smem_get_cpu_addr(struct mtk_fd_smem_drv *smem_dev, + struct scatterlist *sg) +{ + struct device *dev = &smem_dev->pdev->dev; + struct dma_coherent_mem *dma_mem = + dev_get_coherent_memory(dev); + + phys_addr_t addr = (phys_addr_t)sg_phys(sg); + + if (addr < smem_dev->smem_base || + addr > smem_dev->smem_base + smem_dev->smem_size) { + dev_err(dev, "Invalid paddr 0x%llx from sg\n", addr); + return NULL; + } + + return dma_mem->virt_base + (addr - smem_dev->smem_base); +} + +static void mtk_fd_smem_sync_sg_for_cpu(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir) +{ + struct mtk_fd_smem_drv *smem_dev = + dev_get_drvdata(dev); + void *cpu_addr; + + cpu_addr = mtk_fd_smem_get_cpu_addr(smem_dev, sgl); + + dev_dbg(dev, + "__dma_unmap_area:paddr(0x%llx),vaddr(0x%llx),size(%d)\n", + (unsigned long long)sg_phys(sgl), + (unsigned long long)cpu_addr, + sgl->length); + + __dma_unmap_area(cpu_addr, sgl->length, dir); +} + +static void mtk_fd_smem_sync_sg_for_device(struct device *dev, + struct scatterlist *sgl, int nelems, + enum dma_data_direction dir) +{ + struct mtk_fd_smem_drv *smem_dev = + dev_get_drvdata(dev); + void *cpu_addr; + + cpu_addr = mtk_fd_smem_get_cpu_addr(smem_dev, sgl); + + dev_dbg(dev, + "__dma_map_area:paddr(0x%llx),vaddr(0x%llx),size(%d)\n", + (unsigned long long)sg_phys(sgl), + (unsigned long long)cpu_addr, + sgl->length); + + __dma_map_area(cpu_addr, sgl->length, dir); +} + +static int mtk_fd_smem_setup_dma_ops(struct device *dev, + const struct dma_map_ops *smem_ops) +{ + if (!dev->dma_ops) + return -EINVAL; + + memcpy((void *)smem_ops, dev->dma_ops, sizeof(*smem_ops)); + + ((struct dma_map_ops *)smem_ops)->get_sgtable = + mtk_fd_smem_get_sgtable; + ((struct dma_map_ops *)smem_ops)->sync_sg_for_device = + mtk_fd_smem_sync_sg_for_device; + ((struct dma_map_ops *)smem_ops)->sync_sg_for_cpu = + mtk_fd_smem_sync_sg_for_cpu; + + dev->dma_ops = smem_ops; + + return 0; +} + +void mtk_fd_smem_enable_mpu(struct device *dev) +{ + dev_warn(dev, "MPU enabling func is not ready now\n"); +} + +MODULE_AUTHOR("Frederic Chen <frederic.chen@xxxxxxxxxxxx>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek FD shared memory driver"); diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-smem.h b/drivers/media/platform/mtk-isp/fd/mtk_fd-smem.h new file mode 100644 index 0000000..a19fc37 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-smem.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 __MTK_FD_SMEM_H__ +#define __MTK_FD_SMEM_H__ + +#include <linux/dma-mapping.h> + +phys_addr_t mtk_fd_smem_iova_to_phys(struct device *smem_dev, + dma_addr_t iova); +void mtk_fd_smem_enable_mpu(struct device *smem_dev); + +#endif /*__MTK_FD_SMEM_H__*/ diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2-util.c b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2-util.c new file mode 100644 index 0000000..ab85aea --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2-util.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Mediatek Corporation. + * Copyright (c) 2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License 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. + * + * MTK_FD-v4l2 is highly based on Intel IPU 3 chrome driver + * + */ + +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/videodev2.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-subdev.h> +#include <media/v4l2-event.h> +#include <linux/device.h> +#include <linux/platform_device.h> + +#include "mtk_fd-dev.h" + +static u32 mtk_fd_node_get_v4l2_cap + (struct mtk_fd_ctx_queue *node_ctx); + +static int mtk_fd_videoc_s_meta_fmt(struct file *file, void *fh, + struct v4l2_format *f); + +static int mtk_fd_subdev_open(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct mtk_fd_dev *fd_dev = mtk_fd_subdev_to_dev(sd); + + fd_dev->ctx.fh = fh; + + return mtk_fd_ctx_open(&fd_dev->ctx); +} + +static int mtk_fd_subdev_close(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct mtk_fd_dev *fd_dev = mtk_fd_subdev_to_dev(sd); + + return mtk_fd_ctx_release(&fd_dev->ctx); +} + +static int mtk_fd_subdev_s_stream(struct v4l2_subdev *sd, int enable) +{ + int ret = 0; + + struct mtk_fd_dev *fd_dev = mtk_fd_subdev_to_dev(sd); + + if (enable) { + ret = mtk_fd_ctx_streamon(&fd_dev->ctx); + + if (!ret) + ret = mtk_fd_dev_queue_buffers + (mtk_fd_ctx_to_dev(&fd_dev->ctx), true); + if (ret) + pr_err("failed to queue initial buffers (%d)", ret); + } else { + ret = mtk_fd_ctx_streamoff(&fd_dev->ctx); + } + + if (!ret) + fd_dev->mem2mem2.streaming = enable; + + return ret; +} + +static int mtk_fd_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct mtk_fd_mem2mem2_device *m2m2 = + container_of(entity, struct mtk_fd_mem2mem2_device, + subdev.entity); + struct mtk_fd_dev *fd_dev = + container_of(m2m2, struct mtk_fd_dev, mem2mem2); + + u32 pad = local->index; + + pr_info("link setup: %d --> %d\n", pad, remote->index); + +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + WARN_ON(entity->type != MEDIA_ENT_T_V4L2_SUBDEV); +#else + WARN_ON(entity->obj_type != MEDIA_ENTITY_TYPE_V4L2_SUBDEV); +#endif + + WARN_ON(pad >= m2m2->num_nodes); + + m2m2->nodes[pad].enabled = !!(flags & MEDIA_LNK_FL_ENABLED); + + /** + * queue_enable can be phase out in the future since + * we don't have internal queue of each node in + * v4l2 common module + */ + fd_dev->queue_enabled[pad] = m2m2->nodes[pad].enabled; + + return 0; +} + +static void mtk_fd_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_fd_mem2mem2_device *m2m2 = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_fd_dev *mtk_fd_dev = mtk_fd_m2m_to_dev(m2m2); + struct device *dev = &mtk_fd_dev->pdev->dev; + struct mtk_fd_dev_buffer *buf = NULL; + struct vb2_v4l2_buffer *v4l2_buf = NULL; + struct mtk_fd_dev_video_device *node = + mtk_fd_vbq_to_isp_node(vb->vb2_queue); + int queue = mtk_fd_dev_get_queue_id_of_dev_node(mtk_fd_dev, node); + + dev_dbg(dev, + "queue vb2_buf: Node(%s) queue id(%d)\n", + node->name, + queue); + + if (queue < 0) { + dev_err(m2m2->dev, "Invalid mtk_fd_dev node.\n"); + return; + } + + if (mtk_fd_dev->ctx.mode == MTK_FD_CTX_MODE_DEBUG_BYPASS_ALL) { + dev_dbg(m2m2->dev, "By pass mode, just loop back the buffer\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + return; + } + + if (!vb) + pr_err("VB can't be null\n"); + + buf = mtk_fd_vb2_buf_to_dev_buf(vb); + + if (!buf) + pr_err("buf can't be null\n"); + + v4l2_buf = to_vb2_v4l2_buffer(vb); + + if (!v4l2_buf) + pr_err("v4l2_buf can't be null\n"); + + mutex_lock(&mtk_fd_dev->lock); + + pr_err("init mtk_fd_ctx_buf_init, sequence(%d)\n", v4l2_buf->sequence); + + /* the dma address will be filled in later frame buffer handling*/ + mtk_fd_ctx_buf_init(&buf->ctx_buf, queue, (dma_addr_t)0); + pr_info("set mtk_fd_ctx_buf_init: user seq=%d\n", + buf->ctx_buf.user_sequence); + + /* Added the buffer into the tracking list */ + list_add_tail(&buf->m2m2_buf.list, + &m2m2->nodes[node - m2m2->nodes].buffers); + mutex_unlock(&mtk_fd_dev->lock); + + /* Enqueue the buffer */ + if (mtk_fd_dev->mem2mem2.streaming) { + pr_info("%s: mtk_fd_dev_queue_buffers\n", node->name); + mtk_fd_dev_queue_buffers(mtk_fd_dev, false); + } +} + +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION +static int mtk_fd_vb2_queue_setup(struct vb2_queue *vq, + const void *parg, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) +#else +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[]) +#endif +{ + struct mtk_fd_mem2mem2_device *m2m2 = vb2_get_drv_priv(vq); + struct mtk_fd_dev_video_device *node = mtk_fd_vbq_to_isp_node(vq); + struct mtk_fd_dev *fd_dev = mtk_fd_m2m_to_dev(m2m2); + void *buf_alloc_ctx = NULL; + int queue = mtk_fd_dev_get_queue_id_of_dev_node(fd_dev, node); + /* Get V4L2 format with the following method */ + const struct v4l2_format *fmt = &node->vdev_fmt; + + *num_planes = 1; + *num_buffers = clamp_val(*num_buffers, 1, VB2_MAX_FRAME); + + vq->dma_attrs |= DMA_ATTR_NON_CONSISTENT; + pr_info("queue(%d): cached mmap enabled\n", queue); + + if (vq->type == V4L2_BUF_TYPE_META_CAPTURE || + vq->type == V4L2_BUF_TYPE_META_OUTPUT) { + sizes[0] = fmt->fmt.meta.buffersize; + buf_alloc_ctx = fd_dev->ctx.smem_vb2_alloc_ctx; + pr_info("Select smem_vb2_alloc_ctx(%llx)\n", + (unsigned long long)buf_alloc_ctx); + } else { + sizes[0] = fmt->fmt.pix_mp.plane_fmt[0].sizeimage; + buf_alloc_ctx = fd_dev->ctx.img_vb2_alloc_ctx; + pr_info("Select img_vb2_alloc_ctx(%llx)\n", + (unsigned long long)buf_alloc_ctx); + } + +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + alloc_ctxs[0] = buf_alloc_ctx; +#else + alloc_devs[0] = (struct device *)buf_alloc_ctx; +#endif + + pr_info("mtk_fd_vb2_queue_setup:type(%d),size(%d),ctx(%llx)\n", + vq->type, sizes[0], (unsigned long long)buf_alloc_ctx); + + /* Initialize buffer queue */ + INIT_LIST_HEAD(&node->buffers); + + return 0; +} + +static bool mtk_fd_all_nodes_streaming(struct mtk_fd_mem2mem2_device *m2m2, + struct mtk_fd_dev_video_device *except) +{ + int i; + + for (i = 0; i < m2m2->num_nodes; i++) { + struct mtk_fd_dev_video_device *node = &m2m2->nodes[i]; + + if (node == except) + continue; + if (node->enabled && !vb2_start_streaming_called(&node->vbq)) + return false; + } + + return true; +} + +static void mtk_fd_return_all_buffers(struct mtk_fd_mem2mem2_device *m2m2, + struct mtk_fd_dev_video_device *node, + enum vb2_buffer_state state) +{ + struct mtk_fd_dev *mtk_fd_dev = mtk_fd_m2m_to_dev(m2m2); + struct mtk_fd_mem2mem2_buffer *b, *b0; + + /* Return all buffers */ + mutex_lock(&mtk_fd_dev->lock); + list_for_each_entry_safe(b, b0, &node->buffers, list) { + list_del(&b->list); + vb2_buffer_done(&b->vbb.vb2_buf, state); + } + mutex_unlock(&mtk_fd_dev->lock); +} + +static int mtk_fd_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mtk_fd_mem2mem2_device *m2m2 = vb2_get_drv_priv(vq); + struct mtk_fd_dev_video_device *node = + mtk_fd_vbq_to_isp_node(vq); + int r; + + if (m2m2->streaming) { + r = -EBUSY; + goto fail_return_bufs; + } + + if (!node->enabled) { + pr_err("Node (%ld) is not enable\n", node - m2m2->nodes); + r = -EINVAL; + goto fail_return_bufs; + } +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + r = media_entity_pipeline_start(&node->vdev.entity, &m2m2->pipeline); +#else + r = media_pipeline_start(&node->vdev.entity, &m2m2->pipeline); +#endif + if (r < 0) { + pr_err("Node (%ld) media_pipeline_start failed\n", + node - m2m2->nodes); + goto fail_return_bufs; + } + + if (!mtk_fd_all_nodes_streaming(m2m2, node)) + return 0; + + /* Start streaming of the whole pipeline now */ + + r = v4l2_subdev_call(&m2m2->subdev, video, s_stream, 1); + if (r < 0) { + pr_err("Node (%ld) v4l2_subdev_call s_stream failed\n", + node - m2m2->nodes); + goto fail_stop_pipeline; + } + return 0; + +fail_stop_pipeline: +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + media_entity_pipeline_stop(&node->vdev.entity); +#else + media_pipeline_stop(&node->vdev.entity); +#endif +fail_return_bufs: + mtk_fd_return_all_buffers(m2m2, node, VB2_BUF_STATE_QUEUED); + + return r; +} + +static void mtk_fd_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct mtk_fd_mem2mem2_device *m2m2 = vb2_get_drv_priv(vq); + struct mtk_fd_dev_video_device *node = + mtk_fd_vbq_to_isp_node(vq); + int r; + + WARN_ON(!node->enabled); + + /* Was this the first node with streaming disabled? */ + if (mtk_fd_all_nodes_streaming(m2m2, node)) { + /* Yes, really stop streaming now */ + r = v4l2_subdev_call(&m2m2->subdev, video, s_stream, 0); + if (r) + dev_err(m2m2->dev, "failed to stop streaming\n"); + } + + mtk_fd_return_all_buffers(m2m2, node, VB2_BUF_STATE_ERROR); +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + media_entity_pipeline_stop(&node->vdev.entity); +#else + media_pipeline_stop(&node->vdev.entity); +#endif +} + +static int mtk_fd_videoc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct mtk_fd_mem2mem2_device *m2m2 = video_drvdata(file); + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + struct mtk_fd_dev *fd_dev = mtk_fd_m2m_to_dev(m2m2); + int queue_id = + mtk_fd_dev_get_queue_id_of_dev_node(fd_dev, node); + struct mtk_fd_ctx_queue *node_ctx = &fd_dev->ctx.queue[queue_id]; + + strlcpy(cap->driver, m2m2->name, sizeof(cap->driver)); + strlcpy(cap->card, m2m2->model, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", node->name); + + cap->device_caps = + mtk_fd_node_get_v4l2_cap(node_ctx) | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +/* Propagate forward always the format from the CIO2 subdev */ +static int mtk_fd_videoc_g_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + + f->fmt = node->vdev_fmt.fmt; + + return 0; +} + +static int mtk_fd_videoc_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_fd_mem2mem2_device *m2m2 = video_drvdata(file); + struct mtk_fd_dev *fd_dev = mtk_fd_m2m_to_dev(m2m2); + struct mtk_fd_ctx *dev_ctx = &fd_dev->ctx; + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + int queue_id = + mtk_fd_dev_get_queue_id_of_dev_node(fd_dev, node); + int ret = 0; + + ret = mtk_fd_ctx_fmt_set_img(dev_ctx, queue_id, &f->fmt.pix_mp, + &node->vdev_fmt.fmt.pix_mp); + + /* Simply set the format to the node context in the initial version */ + if (ret) { + pr_warn("Fmt(%d) not support for queue(%d), load default fmt\n", + f->fmt.pix_mp.pixelformat, queue_id); + + ret = mtk_fd_ctx_format_load_default_fmt + (&dev_ctx->queue[queue_id], f); + } + + if (!ret) { + node->vdev_fmt.fmt.pix_mp = f->fmt.pix_mp; + dev_ctx->queue[queue_id].fmt.pix_mp = node->vdev_fmt.fmt.pix_mp; + } + + return ret; +} + +static int mtk_fd_videoc_s_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_fd_mem2mem2_device *m2m2 = video_drvdata(file); + struct mtk_fd_dev *fd_dev = mtk_fd_m2m_to_dev(m2m2); + struct mtk_fd_ctx *dev_ctx = &fd_dev->ctx; + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + int queue_id = mtk_fd_dev_get_queue_id_of_dev_node(fd_dev, node); + int ret = 0; + + ret = mtk_fd_ctx_fmt_set_img(dev_ctx, queue_id, &f->fmt.pix_mp, + &node->vdev_fmt.fmt.pix_mp); + + /* Simply set the format to the node context in the initial version */ + if (!ret) + dev_ctx->queue[queue_id].fmt.pix_mp = node->vdev_fmt.fmt.pix_mp; + else + dev_warn(&fd_dev->pdev->dev, "s_fmt, format not support\n"); + + return ret; +} + +static int mtk_fd_meta_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + + if (f->index > 0 || f->type != node->vbq.type) + return -EINVAL; + + f->pixelformat = node->vdev_fmt.fmt.meta.dataformat; + + return 0; +} + +static int mtk_fd_videoc_s_meta_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_fd_mem2mem2_device *m2m2 = video_drvdata(file); + struct mtk_fd_dev *fd_dev = mtk_fd_m2m_to_dev(m2m2); + struct mtk_fd_ctx *dev_ctx = &fd_dev->ctx; + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + int queue_id = mtk_fd_dev_get_queue_id_of_dev_node(fd_dev, node); + + int ret = 0; + + if (f->type != node->vbq.type) + return -EINVAL; + + ret = mtk_fd_ctx_format_load_default_fmt(&dev_ctx->queue[queue_id], f); + + if (!ret) { + node->vdev_fmt.fmt.meta = f->fmt.meta; + dev_ctx->queue[queue_id].fmt.meta = node->vdev_fmt.fmt.meta; + } else { + dev_warn(&fd_dev->pdev->dev, + "s_meta_fm failed, format not support\n"); + } + + return ret; +} + +static int mtk_fd_videoc_g_meta_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_fd_dev_video_device *node = file_to_mtk_fd_node(file); + + if (f->type != node->vbq.type) + return -EINVAL; + + f->fmt = node->vdev_fmt.fmt; + + return 0; +} + +int mtk_fd_videoc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct video_device *vdev = video_devdata(file); + struct vb2_buffer *vb; + struct mtk_fd_dev_buffer *db; + int r = 0; + + /* check if vb2 queue is busy */ + if (vdev->queue->owner && vdev->queue->owner != file->private_data) + return -EBUSY; + + /** + * Keep the value of sequence in v4l2_buffer + * in ctx buf since vb2_qbuf will set it to 0 + */ + vb = vdev->queue->bufs[p->index]; + + if (vb) { + db = mtk_fd_vb2_buf_to_dev_buf(vb); + pr_info("qbuf: p:%llx,vb:%llx, db:%llx\n", + (unsigned long long)p, + (unsigned long long)vb, + (unsigned long long)db); + db->ctx_buf.user_sequence = p->sequence; + } + r = vb2_qbuf(vdev->queue, vdev->v4l2_dev->mdev, p); + if (r) + pr_err("vb2_qbuf failed(err=%d): buf idx(%d)\n", r, p->index); + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_videoc_qbuf); + +/******************** function pointers ********************/ + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops mtk_fd_subdev_internal_ops = { + .open = mtk_fd_subdev_open, + .close = mtk_fd_subdev_close, +}; + +static const struct v4l2_subdev_core_ops mtk_fd_subdev_core_ops = { +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, +#endif +}; + +static const struct v4l2_subdev_video_ops mtk_fd_subdev_video_ops = { + .s_stream = mtk_fd_subdev_s_stream, +}; + +static const struct v4l2_subdev_ops mtk_fd_subdev_ops = { + .core = &mtk_fd_subdev_core_ops, + .video = &mtk_fd_subdev_video_ops, +}; + +static const struct media_entity_operations mtk_fd_media_ops = { + .link_setup = mtk_fd_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct vb2_ops mtk_fd_vb2_ops = { + .buf_queue = mtk_fd_vb2_buf_queue, + .queue_setup = mtk_fd_vb2_queue_setup, + .start_streaming = mtk_fd_vb2_start_streaming, + .stop_streaming = mtk_fd_vb2_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static const struct v4l2_file_operations mtk_fd_v4l2_fops = { + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif +}; + +static const struct v4l2_ioctl_ops mtk_fd_v4l2_ioctl_ops = { + .vidioc_querycap = mtk_fd_videoc_querycap, + + .vidioc_g_fmt_vid_cap_mplane = mtk_fd_videoc_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = mtk_fd_videoc_s_fmt, + .vidioc_try_fmt_vid_cap_mplane = mtk_fd_videoc_try_fmt, + + .vidioc_g_fmt_vid_out_mplane = mtk_fd_videoc_g_fmt, + .vidioc_s_fmt_vid_out_mplane = mtk_fd_videoc_s_fmt, + .vidioc_try_fmt_vid_out_mplane = mtk_fd_videoc_try_fmt, + + /* buffer queue management */ + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = mtk_fd_videoc_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, +}; + +static const struct v4l2_ioctl_ops mtk_fd_v4l2_meta_ioctl_ops = { + .vidioc_querycap = mtk_fd_videoc_querycap, + + .vidioc_enum_fmt_meta_cap = mtk_fd_meta_enum_format, + .vidioc_g_fmt_meta_cap = mtk_fd_videoc_g_meta_fmt, + .vidioc_s_fmt_meta_cap = mtk_fd_videoc_s_meta_fmt, + .vidioc_try_fmt_meta_cap = mtk_fd_videoc_g_meta_fmt, + + .vidioc_enum_fmt_meta_out = mtk_fd_meta_enum_format, + .vidioc_g_fmt_meta_out = mtk_fd_videoc_g_meta_fmt, + .vidioc_s_fmt_meta_out = mtk_fd_videoc_s_meta_fmt, + .vidioc_try_fmt_meta_out = mtk_fd_videoc_g_meta_fmt, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = mtk_fd_videoc_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, +}; + +static u32 mtk_fd_node_get_v4l2_cap(struct mtk_fd_ctx_queue *node_ctx) +{ + u32 cap = 0; + + if (node_ctx->desc.capture) + if (node_ctx->desc.image) + cap = V4L2_CAP_VIDEO_CAPTURE_MPLANE; + else + cap = V4L2_CAP_META_CAPTURE; + else + if (node_ctx->desc.image) + cap = V4L2_CAP_VIDEO_OUTPUT_MPLANE; + else + cap = V4L2_CAP_META_OUTPUT; + + return cap; +} + +static u32 mtk_fd_node_get_format_type(struct mtk_fd_ctx_queue *node_ctx) +{ + u32 type; + + if (node_ctx->desc.capture) + if (node_ctx->desc.image) + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + else + type = V4L2_BUF_TYPE_META_CAPTURE; + else + if (node_ctx->desc.image) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + type = V4L2_BUF_TYPE_META_OUTPUT; + + return type; +} + +static const struct v4l2_ioctl_ops *mtk_fd_node_get_ioctl_ops + (struct mtk_fd_ctx_queue *node_ctx) +{ + const struct v4l2_ioctl_ops *ops = NULL; + + if (node_ctx->desc.image) + ops = &mtk_fd_v4l2_ioctl_ops; + else + ops = &mtk_fd_v4l2_meta_ioctl_ops; + return ops; +} + +/** + * Config node's video properties + * according to the device context requirement + */ +static void mtk_fd_node_to_v4l2(struct mtk_fd_dev *fd_dev, u32 node, + struct video_device *vdev, + struct v4l2_format *f) +{ + u32 cap; + struct mtk_fd_ctx *device_ctx = &fd_dev->ctx; + struct mtk_fd_ctx_queue *node_ctx = &device_ctx->queue[node]; + + WARN_ON(node >= mtk_fd_dev_get_total_node(fd_dev)); + WARN_ON(!node_ctx); + + /* set cap of the node */ + cap = mtk_fd_node_get_v4l2_cap(node_ctx); + f->type = mtk_fd_node_get_format_type(node_ctx); + vdev->ioctl_ops = mtk_fd_node_get_ioctl_ops(node_ctx); + + if (mtk_fd_ctx_format_load_default_fmt(&device_ctx->queue[node], f)) { + dev_err(&fd_dev->pdev->dev, + "Can't load default for node (%d): (%s)", + node, device_ctx->queue[node].desc.name); + } else { + if (device_ctx->queue[node].desc.image) { + dev_dbg(&fd_dev->pdev->dev, + "Node (%d): (%s), dfmt (f:0x%x w:%d: h:%d s:%d)\n", + node, device_ctx->queue[node].desc.name, + f->fmt.pix_mp.pixelformat, + f->fmt.pix_mp.width, + f->fmt.pix_mp.height, + f->fmt.pix_mp.plane_fmt[0].sizeimage); + node_ctx->fmt.pix_mp = f->fmt.pix_mp; + } else { + dev_info(&fd_dev->pdev->dev, + "Node (%d): (%s), dfmt (f:0x%x s:%u)\n", + node, device_ctx->queue[node].desc.name, + f->fmt.meta.dataformat, + f->fmt.meta.buffersize); + node_ctx->fmt.meta = f->fmt.meta; + } + } + +#if KERNEL_VERSION(4, 7, 0) < MTK_FD_KERNEL_BASE_VERSION + /* device_caps was supported after 4.7 */ + vdev->device_caps = V4L2_CAP_STREAMING | cap; +#endif +} + +int mtk_fd_media_register(struct device *dev, struct media_device *media_dev, + const char *model) +{ + int r = 0; + + media_dev->dev = dev; + dev_info(dev, "setup media_dev.dev: %llx\n", + (unsigned long long)media_dev->dev); + + strlcpy(media_dev->model, model, sizeof(media_dev->model)); + dev_info(dev, "setup media_dev.model: %s\n", + media_dev->model); + + snprintf(media_dev->bus_info, sizeof(media_dev->bus_info), + "%s", dev_name(dev)); + dev_info(dev, "setup media_dev.bus_info: %s\n", + media_dev->bus_info); + + media_dev->hw_revision = 0; + dev_info(dev, "setup media_dev.hw_revision: %d\n", + media_dev->hw_revision); + +#if KERNEL_VERSION(4, 5, 0) <= MTK_FD_KERNEL_BASE_VERSION + dev_info(dev, "media_device_init: media_dev:%llx\n", + (unsigned long long)media_dev); + media_device_init(media_dev); +#endif + + pr_info("Register media device: %s, %llx", + media_dev->model, + (unsigned long long)media_dev); + + r = media_device_register(media_dev); + + if (r) { + dev_err(dev, "failed to register media device (%d)\n", r); + goto fail_v4l2_dev; + } + return 0; + +fail_v4l2_dev: + media_device_unregister(media_dev); +#if KERNEL_VERSION(4, 5, 0) <= MTK_FD_KERNEL_BASE_VERSION + media_device_cleanup(media_dev); +#endif + + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_media_register); + +int mtk_fd_v4l2_register(struct device *dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev) +{ + int r = 0; + /* Set up v4l2 device */ + v4l2_dev->mdev = media_dev; + dev_info(dev, "setup v4l2_dev->mdev: %llx", + (unsigned long long)v4l2_dev->mdev); + + dev_info(dev, "Register v4l2 device: %llx", + (unsigned long long)v4l2_dev); + + r = v4l2_device_register(dev, v4l2_dev); + + if (r) { + dev_err(dev, "failed to register V4L2 device (%d)\n", r); + goto fail_v4l2_dev; + } + + return 0; + +fail_v4l2_dev: + media_device_unregister(media_dev); +#if KERNEL_VERSION(4, 5, 0) <= MTK_FD_KERNEL_BASE_VERSION + media_device_cleanup(media_dev); +#endif + + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_v4l2_register); + +int mtk_fd_mem2mem2_v4l2_register(struct mtk_fd_dev *dev, + struct media_device *media_dev, + struct v4l2_device *v4l2_dev) +{ + struct mtk_fd_mem2mem2_device *m2m2 = &dev->mem2mem2; + + int i, r; + + /** + * If media_dev or v4l2_dev is not set, + * use the default one in mtk_fd_dev + */ + if (!media_dev) { + m2m2->media_dev = &dev->media_dev; + r = mtk_fd_media_register(&dev->pdev->dev, m2m2->media_dev, + m2m2->model); + + if (r) { + dev_err(m2m2->dev, "failed to register media device (%d)\n", r); + goto fail_media_dev; + } + } else { + m2m2->media_dev = media_dev; + } + + if (!v4l2_dev) { + m2m2->v4l2_dev = &dev->v4l2_dev; + r = mtk_fd_v4l2_register(&dev->pdev->dev, + m2m2->media_dev, + m2m2->v4l2_dev); + if (r) { + dev_err(m2m2->dev, "failed to register V4L2 device (%d)\n", r); + goto fail_v4l2_dev; + } + } else { + m2m2->v4l2_dev = v4l2_dev; + } + + /* Initialize miscellaneous variables */ + m2m2->streaming = false; + m2m2->v4l2_file_ops = mtk_fd_v4l2_fops; + + /* Initialize subdev media entity */ + m2m2->subdev_pads = kcalloc(m2m2->num_nodes, sizeof(*m2m2->subdev_pads), + GFP_KERNEL); + if (!m2m2->subdev_pads) { + r = -ENOMEM; + goto fail_subdev_pads; + } +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + r = media_entity_init(&m2m2->subdev.entity, m2m2->num_nodes, + m2m2->subdev_pads, 0); +#else + r = media_entity_pads_init(&m2m2->subdev.entity, m2m2->num_nodes, + m2m2->subdev_pads); +#endif + if (r) { + dev_err(m2m2->dev, + "failed initialize subdev media entity (%d)\n", r); + goto fail_media_entity; + } + + /* Initialize subdev */ + v4l2_subdev_init(&m2m2->subdev, &mtk_fd_subdev_ops); + + m2m2->subdev.entity.function = + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + + m2m2->subdev.entity.ops = &mtk_fd_media_ops; + + for (i = 0; i < m2m2->num_nodes; i++) { + m2m2->subdev_pads[i].flags = m2m2->nodes[i].output ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + m2m2->subdev.flags = + V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + snprintf(m2m2->subdev.name, sizeof(m2m2->subdev.name), + "%s", m2m2->name); + v4l2_set_subdevdata(&m2m2->subdev, m2m2); + m2m2->subdev.internal_ops = &mtk_fd_subdev_internal_ops; + + pr_info("register subdev: %s\n", m2m2->subdev.name); + r = v4l2_device_register_subdev(m2m2->v4l2_dev, &m2m2->subdev); + if (r) { + dev_err(m2m2->dev, "failed initialize subdev (%d)\n", r); + goto fail_subdev; + } + r = v4l2_device_register_subdev_nodes(m2m2->v4l2_dev); + if (r) { + dev_err(m2m2->dev, "failed to register subdevs (%d)\n", r); + goto fail_subdevs; + } + + /* Create video nodes and links */ + for (i = 0; i < m2m2->num_nodes; i++) { + struct mtk_fd_dev_video_device *node = &m2m2->nodes[i]; + struct video_device *vdev = &node->vdev; + struct vb2_queue *vbq = &node->vbq; + u32 flags; + + /* Initialize miscellaneous variables */ + mutex_init(&node->lock); + INIT_LIST_HEAD(&node->buffers); + + /* Initialize formats to default values */ + mtk_fd_node_to_v4l2(dev, i, vdev, &node->vdev_fmt); + + /* Initialize media entities */ +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + r = media_entity_init(&vdev->entity, 1, &node->vdev_pad, 0); +#else + r = media_entity_pads_init(&vdev->entity, 1, &node->vdev_pad); +#endif + if (r) { + dev_err(m2m2->dev, + "failed initialize media entity (%d)\n", r); + goto fail_vdev_media_entity; + } + node->vdev_pad.flags = node->output ? + MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK; + vdev->entity.ops = NULL; + + /* Initialize vbq */ + vbq->type = node->vdev_fmt.type; + vbq->io_modes = VB2_MMAP | VB2_DMABUF; + vbq->ops = &mtk_fd_vb2_ops; + vbq->mem_ops = m2m2->vb2_mem_ops; + m2m2->buf_struct_size = sizeof(struct mtk_fd_dev_buffer); + vbq->buf_struct_size = m2m2->buf_struct_size; + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vbq->min_buffers_needed = 0; /* Can streamon w/o buffers */ + /* Put the process hub sub device in the vb2 private data*/ + vbq->drv_priv = m2m2; + vbq->lock = &node->lock; + r = vb2_queue_init(vbq); + if (r) { + dev_err(m2m2->dev, + "failed to initialize video queue (%d)\n", r); + goto fail_vdev; + } + + /* Initialize vdev */ + snprintf(vdev->name, sizeof(vdev->name), "%s %s", + m2m2->name, node->name); + vdev->release = video_device_release_empty; + vdev->fops = &m2m2->v4l2_file_ops; + vdev->lock = &node->lock; + vdev->v4l2_dev = m2m2->v4l2_dev; + vdev->queue = &node->vbq; + vdev->vfl_dir = node->output ? VFL_DIR_TX : VFL_DIR_RX; + video_set_drvdata(vdev, m2m2); + pr_info("register vdev: %s\n", vdev->name); + r = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (r) { + dev_err(m2m2->dev, + "failed to register video device (%d)\n", r); + goto fail_vdev; + } + + /* Create link between video node and the subdev pad */ + flags = 0; + if (node->enabled) + flags |= MEDIA_LNK_FL_ENABLED; + if (node->immutable) + flags |= MEDIA_LNK_FL_IMMUTABLE; + if (node->output) { +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + r = media_entity_create_link +#else + r = media_create_pad_link +#endif + (&vdev->entity, 0, + &m2m2->subdev.entity, + i, flags); + } else { +#if KERNEL_VERSION(4, 5, 0) >= MTK_FD_KERNEL_BASE_VERSION + r = media_entity_create_link +#else + r = media_create_pad_link +#endif + (&m2m2->subdev.entity, + i, &vdev->entity, 0, + flags); + } + if (r) + goto fail_link; + } + + return 0; + + for (; i >= 0; i--) { +fail_link: + video_unregister_device(&m2m2->nodes[i].vdev); +fail_vdev: + media_entity_cleanup(&m2m2->nodes[i].vdev.entity); +fail_vdev_media_entity: + mutex_destroy(&m2m2->nodes[i].lock); + } +fail_subdevs: + v4l2_device_unregister_subdev(&m2m2->subdev); +fail_subdev: + media_entity_cleanup(&m2m2->subdev.entity); +fail_media_entity: + kfree(m2m2->subdev_pads); +fail_subdev_pads: + v4l2_device_unregister(m2m2->v4l2_dev); +fail_v4l2_dev: +fail_media_dev: + pr_err("fail_v4l2_dev: media_device_unregister and clenaup:%llx", + (unsigned long long)m2m2->media_dev); + media_device_unregister(m2m2->media_dev); +#if KERNEL_VERSION(4, 5, 0) <= MTK_FD_KERNEL_BASE_VERSION + media_device_cleanup(m2m2->media_dev); +#endif + + return r; +} +EXPORT_SYMBOL_GPL(mtk_fd_mem2mem2_v4l2_register); + +int mtk_fd_v4l2_unregister(struct mtk_fd_dev *dev) +{ + struct mtk_fd_mem2mem2_device *m2m2 = &dev->mem2mem2; + unsigned int i; + + for (i = 0; i < m2m2->num_nodes; i++) { + video_unregister_device(&m2m2->nodes[i].vdev); + media_entity_cleanup(&m2m2->nodes[i].vdev.entity); + mutex_destroy(&m2m2->nodes[i].lock); + } + + v4l2_device_unregister_subdev(&m2m2->subdev); + media_entity_cleanup(&m2m2->subdev.entity); + kfree(m2m2->subdev_pads); + v4l2_device_unregister(m2m2->v4l2_dev); + media_device_unregister(m2m2->media_dev); +#if KERNEL_VERSION(4, 5, 0) <= MTK_FD_KERNEL_BASE_VERSION + media_device_cleanup(m2m2->media_dev); +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_fd_v4l2_unregister); + +void mtk_fd_v4l2_buffer_done(struct vb2_buffer *vb, + enum vb2_buffer_state state) +{ + struct mtk_fd_mem2mem2_buffer *b = + container_of(vb, struct mtk_fd_mem2mem2_buffer, vbb.vb2_buf); + + list_del(&b->list); + vb2_buffer_done(&b->vbb.vb2_buf, state); +} +EXPORT_SYMBOL_GPL(mtk_fd_v4l2_buffer_done); diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.c b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.c new file mode 100644 index 0000000..bd447b7 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include "mtk_fd.h" +#include "mtk_fd-ctx.h" +#include "mtk_fd-v4l2.h" + +static struct mtk_fd_ctx_format fd_param_fmts[] = { + { + .fmt.meta = { + .dataformat = V4L2_META_FMT_MTISP_PARAMS, + .max_buffer_size = 1024 * 30, + }, + }, +}; + +static struct mtk_fd_ctx_format in_fmts[] = { + { + .fmt.img = { + .pixelformat = V4L2_PIX_FMT_VYUY, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + }, + }, + { + .fmt.img = { + .pixelformat = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + }, + }, + { + .fmt.img = { + .pixelformat = V4L2_PIX_FMT_YVYU, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + }, + }, + { + .fmt.img = { + .pixelformat = V4L2_PIX_FMT_UYVY, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + }, + }, +}; + +static struct mtk_fd_ctx_queue_desc +output_queues[MTK_FD_CTX_FD_TOTAL_OUTPUT] = { + { + .id = MTK_FD_CTX_FD_YUV_IN, + .name = "FDInput", + .capture = 0, + .image = 1, + .fmts = in_fmts, + .num_fmts = ARRAY_SIZE(in_fmts), + .default_fmt_idx = 1, + }, + { + .id = MTK_FD_CTX_FD_CONFIG_IN, + .name = "FDConfig", + .capture = 0, + .image = 0, + .fmts = fd_param_fmts, + .num_fmts = 1, + .default_fmt_idx = 0, + }, +}; + +static struct mtk_fd_ctx_queue_desc +capture_queues[MTK_FD_CTX_FD_TOTAL_CAPTURE] = { + { + .id = MTK_FD_CTX_FD_OUT, + .name = "FDOutput", + .capture = 1, + .image = 0, + .fmts = fd_param_fmts, + .num_fmts = 1, + .default_fmt_idx = 0, + }, +}; + +static struct mtk_fd_ctx_queues_setting queues_setting = { + .master = MTK_FD_CTX_FD_OUT, + .output_queue_descs = output_queues, + .total_output_queues = MTK_FD_CTX_FD_TOTAL_OUTPUT, + .capture_queue_descs = capture_queues, + .total_capture_queues = MTK_FD_CTX_FD_TOTAL_CAPTURE, +}; + +int mtk_fd_ctx_fd_init(struct mtk_fd_ctx *ctx) +{ + /* Initialize main data structure */ + return mtk_fd_ctx_core_queue_setup(ctx, &queues_setting); +} +EXPORT_SYMBOL_GPL(mtk_fd_ctx_fd_init); diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.h b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.h new file mode 100644 index 0000000..0702abc --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd-v4l2.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018 MediaTek Inc. + * Author: Frederic Chen <frederic.chen@xxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 __MTK_FD_V4L2__ +#define __MTK_FD_V4L2__ + +#include <linux/types.h> +#include "mtk_fd-ctx.h" + +#define MTK_FD_DEV_NAME "MTK-FD-V4L2" + +/* Input: YUV image */ +#define MTK_FD_CTX_FD_YUV_IN 0 +/* Input: FD Configuration */ +#define MTK_FD_CTX_FD_CONFIG_IN 1 +#define MTK_FD_CTX_FD_TOTAL_OUTPUT 2 + +/* OUT: FD output devices*/ +#define MTK_FD_CTX_FD_OUT 2 +#define MTK_FD_CTX_FD_TOTAL_CAPTURE 1 + +int mtk_fd_ctx_fd_init(struct mtk_fd_ctx *ctx); + +#endif /*__MTK_FD_V4L2__*/ diff --git a/drivers/media/platform/mtk-isp/fd/mtk_fd.c b/drivers/media/platform/mtk-isp/fd/mtk_fd.c new file mode 100644 index 0000000..7e2fd00 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd.c @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015 MediaTek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/kdev_t.h> + +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/mm_types.h> +#include <linux/mm.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include <asm/page.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/wait.h> + +#include <linux/of_platform.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/of_device.h> + +#include "mtk_fd.h" +#include "mtk_fd-core.h" + +#include "mtk_vpu.h" + +#include <linux/pm_runtime.h> +#include <linux/clk.h> + +#ifdef CONFIG_PM_WAKELOCKS +#include <linux/pm_wakeup.h> +#else +#include <linux/wakelock.h> +#endif + +#define FD_DRVNAME "mtk-fd" + +static const struct of_device_id mtk_fd_of_ids[] = { + /* Remider: Add this device node manually in .dtsi */ + { .compatible = "mediatek,fd", }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_fd_of_ids); + +static void mtk_fd_prepare_enable_ccf_clock(struct mtk_fd_drv_dev *fd_hw_dev) +{ + int ret; + + pm_runtime_get_sync(fd_hw_dev->larb_dev); + ret = clk_prepare_enable(fd_hw_dev->fd_clk); + if (ret) + dev_err(&fd_hw_dev->pdev->dev, + "cannot prepare and enable CG_IMGSYS_FD clock\n"); +} + +static void mtk_fd_disable_unprepare_ccf_clock(struct mtk_fd_drv_dev *fd_hw_dev) +{ + clk_disable_unprepare(fd_hw_dev->fd_clk); +} + +static int mtk_fd_clk_ctrl(struct mtk_fd_drv_dev *fd_hw_dev, int en) +{ + if (en) + mtk_fd_prepare_enable_ccf_clock(fd_hw_dev); + else + mtk_fd_disable_unprepare_ccf_clock(fd_hw_dev); + + dev_dbg(&fd_hw_dev->pdev->dev, "clock en: %d\n", en); + + return 0; +} + +static int mtk_fd_wait_irq(struct mtk_fd_drv_dev *fd_hw_dev) +{ + int timeout; + + timeout = wait_event_interruptible_timeout + (fd_hw_dev->wq, + (fd_hw_dev->fd_irq_result & FD_IRQ_MASK), + mtk_fd_us_to_jiffies(1 * 1000000)); + + if (timeout == 0) { + dev_err(&fd_hw_dev->pdev->dev, + "%s timeout, %d\n", + __func__, fd_hw_dev->fd_irq_result); + return -EAGAIN; + } + + dev_dbg(&fd_hw_dev->pdev->dev, "irq_res: 0x%8x\n", + fd_hw_dev->fd_irq_result); + + if (timeout != 0 && !(fd_hw_dev->fd_irq_result & FD_IRQ_MASK)) { + dev_err(&fd_hw_dev->pdev->dev, + "%s interrupted by system signal, return value(%d)\n", + __func__, timeout); + return -ERESTARTSYS; + } + + if (!(fd_hw_dev->fd_irq_result & FD_IRQ_MASK)) { + dev_err(&fd_hw_dev->pdev->dev, + "%s Not FD, %d\n", + __func__, fd_hw_dev->fd_irq_result); + return -1; + } + + fd_hw_dev->fd_irq_result = 0; + + return 0; +} + +static int mtk_fd_send_ipi_init(struct platform_device *pdev, + struct fd_buffer *scp_mem) +{ + struct ipi_message fd_init_msg; + + fd_init_msg.cmd_id = FD_CMD_INIT; + fd_init_msg.fd_manager = *scp_mem; + + vpu_ipi_send(pdev, IPI_FD_CMD, &fd_init_msg, sizeof(fd_init_msg)); + + return 0; +} + +static int mtk_fd_send_ipi_cmd(struct platform_device *pdev, + struct v4l2_fd_param *fd_param) +{ + struct ipi_message fd_ipi_msg; + + fd_ipi_msg.cmd_id = FD_CMD_ENQ; + fd_ipi_msg.fd_param = *fd_param; + + vpu_ipi_send_sync_async(pdev, IPI_FD_CMD, &fd_ipi_msg, + sizeof(fd_ipi_msg), 0); + return 0; +} + +static irqreturn_t mtk_fd_irq(int irq, void *dev_addr) +{ + struct mtk_fd_drv_dev *fd_hw_dev; + + fd_hw_dev = (struct mtk_fd_drv_dev *)dev_addr; + fd_hw_dev->fd_irq_result = FD_RD32(fd_hw_dev->fd_base + FD_INT); + wake_up_interruptible(&fd_hw_dev->wq); + + return IRQ_HANDLED; +} + +static int mtk_fd_do_callback(struct mtk_fd_drv_dev *fd_hw_dev, + unsigned int frame_state) +{ + struct mtk_fd_ctx_finish_param fparam; + struct mtk_isp_fd_drv_data *drv_data; + struct device *dev; + int ret = 0; + + drv_data = mtk_fd_hw_dev_to_drv(fd_hw_dev); + dev = &drv_data->fd_hw_dev.pdev->dev; + + fparam.state = frame_state; + fparam.frame_id = fd_hw_dev->fd_ctx.frame_id; + dev_dbg(dev, "frame_id(%d)\n", fparam.frame_id); + + ret = mtk_fd_ctx_core_job_finish(&drv_data->fd_dev.ctx, &fparam); + if (ret) + dev_err(dev, "frame_id(%d), finish op failed: %d\n", + fparam.frame_id, ret); + else + fd_hw_dev->state = FD_CBD; + + return ret; +} + +static int mtk_fd_dequeue(struct mtk_fd_drv_dev *fd_hw_dev, + struct v4l2_fd_param *fd_param) +{ + struct mtk_isp_fd_drv_data *drv_data; + struct fd_user_output *fd_output; + u32 num = 0, i = 0; + + dev_dbg(&fd_hw_dev->pdev->dev, "-E. %s.\n", __func__); + + if ((uint64_t)fd_hw_dev->fd_base < VA_OFFSET) { + dev_info(&fd_hw_dev->pdev->dev, + "-X. %s. fd_base_error: %p\n", + __func__, fd_hw_dev->fd_base); + return -1; + } + + num = FD_RD32(fd_hw_dev->fd_base + FD_RESULT); + FD_WR32(0x0, fd_hw_dev->fd_base + FD_INT_EN); + fd_output = (struct fd_user_output *)fd_param->fd_user_result.va; + fd_output->face_number = num; + + drv_data = mtk_fd_hw_dev_to_drv(fd_hw_dev); + if ((uint64_t)drv_data < VA_OFFSET) { + dev_dbg(&fd_hw_dev->pdev->dev, + "-X. %s. fd_base_error\n", __func__); + return -1; + } + mtk_fd_do_callback(fd_hw_dev, MTK_FD_CTX_FRAME_DATA_DONE); + + for (i = 0; i < num; i++) { + dev_dbg(&fd_hw_dev->pdev->dev, + "id:%d, typ:%d, x0:%d, y0:%d, x1:%d, y1:%d, fcv:0x%x\n", + fd_output->face[i].face_idx, + fd_output->face[i].type, + fd_output->face[i].x0, + fd_output->face[i].y0, + fd_output->face[i].x1, + fd_output->face[i].y1, + fd_output->face[i].fcv); + } + dev_dbg(&fd_hw_dev->pdev->dev, "-X. %s.\n", __func__); + return 0; +} + +static int mtk_fd_manager_buf_init(struct mtk_fd_drv_dev *fd_hw_dev) +{ + struct fd_manager_ctx *manager_ctx; + + manager_ctx = (struct fd_manager_ctx *)fd_hw_dev->fd_ctx.scp_mem.va; + manager_ctx->rs_result = fd_hw_dev->fd_ctx.rs_result; + + return 0; +} + +static int mtk_fd_alloc_rs_buf(struct mtk_fd_drv_dev *fd_hw_dev) +{ + u64 va = 0; + dma_addr_t dma_handle = 0; + u32 size = RS_BUF_SIZE_MAX; + + va = (uint64_t)dma_alloc_coherent(&fd_hw_dev->pdev->dev, size, + &dma_handle, GFP_KERNEL); + + dev_dbg(&fd_hw_dev->pdev->dev, "rsbuffer size: %u\n", size); + dev_dbg(&fd_hw_dev->pdev->dev, "va = 0x%llx, iova = 0x%x\n", va, + dma_handle); + + if (va == 0) { + dev_err(&fd_hw_dev->pdev->dev, "dma_alloc null va!\n"); + return -1; + } + + memset((uint8_t *)va, 0, size); + + fd_hw_dev->fd_ctx.rs_result.va = va; + fd_hw_dev->fd_ctx.rs_result.iova = dma_handle; + + return 0; +} + +static int mtk_fd_get_reserve_mem(struct mtk_fd_drv_dev *fd_hw_dev) +{ + phys_addr_t scp_mem_pa; + u64 scp_mem_va; + u32 scp_mem_iova; + u32 size = 0, size_align = 0; + struct sg_table *sgt; + int n_pages = 0, i = 0, ret = 0; + struct page **pages = NULL; + struct mtk_fd_drv_ctx *fd_ctx; + struct platform_device *pdev = fd_hw_dev->pdev; + + fd_ctx = &fd_hw_dev->fd_ctx; + + scp_mem_iova = 0; + scp_mem_va = vpu_get_reserve_mem_virt(FD_MEM_ID); + scp_mem_pa = vpu_get_reserve_mem_phys(FD_MEM_ID); + size = (u32)vpu_get_reserve_mem_size(FD_MEM_ID); + + dev_dbg(&pdev->dev, "fd_scp_mem va: 0x%llx, pa: 0x%llx, sz:0x%x\n", + scp_mem_va, (u64)scp_mem_pa, size); + + if (scp_mem_va != 0 && size > 0) + memset((void *)scp_mem_va, 0, size); + + /* get iova */ + sgt = &fd_ctx->sgtable; + sg_alloc_table(sgt, 1, GFP_KERNEL); + + size_align = round_up(size, PAGE_SIZE); + n_pages = size_align >> PAGE_SHIFT; + + pages = kmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); + + for (i = 0; i < n_pages; i++) + pages[i] = phys_to_page(scp_mem_pa + i * PAGE_SIZE); + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, + 0, size_align, GFP_KERNEL); + + if (ret) { + dev_err(&pdev->dev, "failed to get allocate sg table\n"); + kfree(pages); + return ret; + } + + dma_map_sg_attrs(&pdev->dev, sgt->sgl, sgt->nents, + DMA_BIDIRECTIONAL, DMA_ATTR_SKIP_CPU_SYNC); + scp_mem_iova = sg_dma_address(sgt->sgl); + kfree(pages); + + dev_dbg(&fd_hw_dev->pdev->dev, "scpmem size:%u,0x%X\n", size, size); + dev_dbg(&fd_hw_dev->pdev->dev, "pa:0x%08X\n", (u32)scp_mem_pa); + dev_dbg(&fd_hw_dev->pdev->dev, "va:0x%16llX\n", (u64)scp_mem_va); + dev_dbg(&fd_hw_dev->pdev->dev, "iova:0x%08X\n", (u32)scp_mem_iova); + + fd_ctx->scp_mem.pa = scp_mem_pa; + fd_ctx->scp_mem.va = scp_mem_va; + fd_ctx->scp_mem.iova = scp_mem_iova; + + return 0; +} + +static int mtk_fd_load_vpu(struct mtk_fd_drv_dev *fd_hw_dev) +{ + struct mtk_fd_drv_ctx *fd_ctx; + int ret = 0; + + fd_ctx = &fd_hw_dev->fd_ctx; + + /* init vpu */ + fd_ctx->vpu_pdev = vpu_get_plat_device(fd_hw_dev->pdev); + + if (!fd_ctx->vpu_pdev) { + dev_err(&fd_hw_dev->pdev->dev, + "Failed to get VPU device\n"); + return -EINVAL; + } + + ret = vpu_load_firmware(fd_ctx->vpu_pdev); + if (ret < 0) { + /** + * Return 0 if downloading firmware successfully, + * otherwise it is failed + */ + dev_err(&fd_hw_dev->pdev->dev, + "vpu_load_firmware failed!"); + return -EINVAL; + } + return ret; +} + +static int mtk_fd_open_context(struct mtk_fd_drv_dev *fd_hw_dev) +{ + struct mtk_fd_drv_ctx *fd_ctx; + + fd_ctx = &fd_hw_dev->fd_ctx; + + mtk_fd_load_vpu(fd_hw_dev); + + mtk_fd_get_reserve_mem(fd_hw_dev); + mtk_fd_alloc_rs_buf(fd_hw_dev); + mtk_fd_manager_buf_init(fd_hw_dev); + + mtk_fd_send_ipi_init(fd_ctx->vpu_pdev, &fd_ctx->scp_mem); + return 0; +} + +static int mtk_fd_release_context(struct mtk_fd_drv_dev *fd_hw_dev) +{ + struct mtk_fd_drv_ctx *fd_ctx; + + fd_ctx = &fd_hw_dev->fd_ctx; + + atomic_set(&fd_ctx->fd_user_cnt, 0); + atomic_set(&fd_ctx->fd_stream_cnt, 0); + atomic_set(&fd_ctx->fd_enque_cnt, 0); + sg_free_table(&fd_ctx->sgtable); + + return 0; +} + +int mtk_fd_open(struct platform_device *pdev) +{ + int ret = 0; + s32 usercount; + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_isp_fd_drv_data *fd_drv; + struct mtk_fd_drv_ctx *fd_ctx; + + dev_dbg(&pdev->dev, "- E. %s.\n", __func__); + + if (!pdev) { + dev_err(&fd_hw_dev->pdev->dev, "platform device is NULL\n"); + return -EINVAL; + } + + fd_drv = dev_get_drvdata(&pdev->dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + fd_ctx = &fd_hw_dev->fd_ctx; + dev_dbg(&fd_hw_dev->pdev->dev, "open fd_hw_dev = 0x%p\n", fd_hw_dev); + dev_dbg(&fd_hw_dev->pdev->dev, "open fd_drv = 0x%p\n", fd_drv); + + usercount = atomic_inc_return(&fd_hw_dev->fd_ctx.fd_user_cnt); + + if (usercount == 1) { + /* Enable clock */ + pm_runtime_get_sync(&fd_hw_dev->pdev->dev); + + mtk_fd_open_context(fd_hw_dev); + fd_hw_dev->state = FD_INI; + fd_hw_dev->streaming = STREAM_OFF; + } + + dev_dbg(&fd_hw_dev->pdev->dev, "usercount = %d", + atomic_read(&fd_ctx->fd_user_cnt)); + + dev_dbg(&fd_hw_dev->pdev->dev, "X. %s\n", __func__); + + return ret; +} +EXPORT_SYMBOL(mtk_fd_open); + +int mtk_fd_streamon(struct platform_device *pdev, u16 id) +{ + int ret = 0; + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_isp_fd_drv_data *fd_drv; + + fd_drv = dev_get_drvdata(&pdev->dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + + dev_dbg(&pdev->dev, "- E. %s\n", __func__); + fd_hw_dev->streaming = STREAM_ON; + atomic_inc_return(&fd_hw_dev->fd_ctx.fd_stream_cnt); + + dev_dbg(&pdev->dev, "- X. %s\n", __func__); + return ret; +} +EXPORT_SYMBOL(mtk_fd_streamon); + +int mtk_fd_enqueue(struct platform_device *pdev, + struct v4l2_fd_param *fd_param) +{ + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_fd_drv_ctx *fd_ctx; + struct mtk_isp_fd_drv_data *fd_drv; + + dev_dbg(&pdev->dev, "- E. %s\n", __func__); + fd_drv = dev_get_drvdata(&pdev->dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + fd_ctx = &fd_hw_dev->fd_ctx; + + fd_ctx->frame_id = fd_param->frame_id; + + if (fd_hw_dev->streaming == STREAM_OFF || fd_hw_dev->state == FD_ENQ) { + dev_err(&fd_hw_dev->pdev->dev, "enqueue before stream on!\n"); + return mtk_fd_do_callback(fd_hw_dev, + MTK_FD_CTX_FRAME_DATA_ERROR); + } + + fd_hw_dev->state = FD_ENQ; + atomic_inc_return(&fd_ctx->fd_enque_cnt); + + if (mtk_fd_send_ipi_cmd(fd_ctx->vpu_pdev, fd_param)) + return -2; + + if (mtk_fd_wait_irq(fd_hw_dev)) + return mtk_fd_do_callback(fd_hw_dev, + MTK_FD_CTX_FRAME_DATA_ERROR); + + mtk_fd_dequeue(fd_hw_dev, fd_param); + + dev_dbg(&pdev->dev, "- E. %s\n", __func__); + return 0; +} +EXPORT_SYMBOL(mtk_fd_enqueue); + +int mtk_fd_release(struct platform_device *pdev) +{ + int ret = 0; + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_isp_fd_drv_data *fd_drv; + + fd_drv = dev_get_drvdata(&pdev->dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + dev_dbg(&fd_hw_dev->pdev->dev, "- E. %s\n", __func__); + + if (!pdev) { + dev_err(&fd_hw_dev->pdev->dev, "platform device is NULL\n"); + return -EINVAL; + } + + dev_info(&fd_hw_dev->pdev->dev, "release fd_hw_dev: 0x%p\n", fd_hw_dev); + + if (atomic_dec_and_test(&fd_hw_dev->fd_ctx.fd_user_cnt)) { + if (fd_hw_dev->state == FD_ENQ) + mtk_fd_wait_irq(fd_hw_dev); + + mtk_fd_release_context(fd_hw_dev); + + pm_runtime_put_sync(&fd_hw_dev->pdev->dev); + } + dev_info(&fd_hw_dev->pdev->dev, "usercount = %d\n", + atomic_read(&fd_hw_dev->fd_ctx.fd_user_cnt)); + + dev_dbg(&fd_hw_dev->pdev->dev, "- X. %s\n", __func__); + return ret; +} +EXPORT_SYMBOL(mtk_fd_release); + +int mtk_fd_streamoff(struct platform_device *pdev, u16 id) +{ + int ret = 0; + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_isp_fd_drv_data *fd_drv; + + fd_drv = dev_get_drvdata(&pdev->dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + + dev_dbg(&pdev->dev, "- E. %s\n", __func__); + + if (fd_hw_dev->state == FD_ENQ) + mtk_fd_wait_irq(fd_hw_dev); + + fd_hw_dev->streaming = STREAM_OFF; + atomic_dec_return(&fd_hw_dev->fd_ctx.fd_stream_cnt); + + dev_dbg(&pdev->dev, "- X. %s\n", __func__); + return ret; +} +EXPORT_SYMBOL(mtk_fd_streamoff); + +static struct mtk_fd_ctx_desc mtk_isp_ctx_desc_fd = { + "proc_device_fd", mtk_fd_ctx_fd_init,}; + +static int mtk_fd_probe(struct platform_device *pdev) +{ + struct mtk_isp_fd_drv_data *fd_drv; + struct mtk_fd_drv_dev *fd_hw_dev; + struct mtk_fd_drv_ctx *fd_ctx; + struct device_node *node; + struct platform_device *larb_pdev; + int irq_num = 0; + int ret = 0; + + dev_info(&pdev->dev, "E. %s.\n", __func__); + + fd_drv = devm_kzalloc(&pdev->dev, sizeof(*fd_drv), GFP_KERNEL); + + if (!fd_drv) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, fd_drv); + fd_hw_dev = &fd_drv->fd_hw_dev; + + if (!fd_hw_dev) { + dev_err(&pdev->dev, "Unable to allocate fd_hw_dev\n"); + return -ENOMEM; + } + + dev_info(&pdev->dev, "open fd_hw_dev = 0x%p\n", fd_hw_dev); + dev_info(&pdev->dev, "open fd_drv = 0x%p\n", fd_drv); + + fd_hw_dev->pdev = pdev; + fd_ctx = &fd_hw_dev->fd_ctx; + + irq_num = irq_of_parse_and_map(pdev->dev.of_node, FD_IRQ_IDX); + ret = request_irq(irq_num, (irq_handler_t)mtk_fd_irq, + IRQF_TRIGGER_NONE, FD_DRVNAME, fd_hw_dev); + if (ret) { + dev_info(&pdev->dev, "%s request_irq fail, irq=%d\n", + __func__, irq_num); + return ret; + } + dev_info(&pdev->dev, "irq_num=%d\n", irq_num); + + node = of_parse_phandle(pdev->dev.of_node, "mediatek,larb", 0); + if (!node) { + dev_err(&pdev->dev, "no mediatek, larb found"); + return -EINVAL; + } + larb_pdev = of_find_device_by_node(node); + if (!larb_pdev) { + dev_err(&pdev->dev, "no mediatek, larb device found"); + return -EINVAL; + } + fd_hw_dev->larb_dev = &larb_pdev->dev; + + node = of_find_compatible_node(NULL, NULL, "mediatek,fd"); + if (!node) { + dev_err(&pdev->dev, "find fd node failed!!!\n"); + return -ENODEV; + } + fd_hw_dev->fd_base = of_iomap(node, 0); + if (!fd_hw_dev->fd_base) { + dev_err(&pdev->dev, "unable to map fd node!!!\n"); + return -ENODEV; + } + dev_info(&pdev->dev, "fd_hw_dev->fd_base: %lx\n", + (unsigned long)fd_hw_dev->fd_base); + + /* CCF: Grab clock pointer (struct clk*) */ + fd_hw_dev->fd_clk = devm_clk_get(&pdev->dev, "FD_CLK_IMG_FD"); + if (IS_ERR(fd_hw_dev->fd_clk)) { + dev_err(&pdev->dev, "cannot get FD_CLK_IMG_FD clock\n"); + return PTR_ERR(fd_hw_dev->fd_clk); + } + + pm_runtime_enable(&pdev->dev); + + atomic_set(&fd_ctx->fd_user_cnt, 0); + atomic_set(&fd_ctx->fd_stream_cnt, 0); + atomic_set(&fd_ctx->fd_enque_cnt, 0); + + init_waitqueue_head(&fd_hw_dev->wq); + + fd_hw_dev->fd_irq_result = 0; + fd_hw_dev->streaming = STREAM_OFF; + + ret = mtk_fd_dev_core_init(pdev, &fd_drv->fd_dev, &mtk_isp_ctx_desc_fd); + + if (ret) + dev_err(&pdev->dev, "v4l2 init failed: %d\n", ret); + + dev_info(&pdev->dev, "X. FD driver probe.\n"); + + return 0; +} + +static int mtk_fd_remove(struct platform_device *pdev) +{ + int i4IRQ = 0; + struct mtk_fd_drv_dev *fd_hw_dev = NULL; + struct mtk_isp_fd_drv_data *drv_data = dev_get_drvdata(&pdev->dev); + + dev_info(&fd_hw_dev->pdev->dev, "-E. %s\n", __func__); + if (drv_data) { + mtk_fd_dev_core_release(pdev, &drv_data->fd_dev); + fd_hw_dev = &drv_data->fd_hw_dev; + } else { + dev_err(&pdev->dev, "Can't find fd driver data\n"); + return -EINVAL; + } + + mtk_fd_clk_ctrl(fd_hw_dev, clock_off); + pm_runtime_disable(&pdev->dev); + + i4IRQ = platform_get_irq(pdev, 0); + free_irq(i4IRQ, NULL); + kfree(fd_hw_dev); + + dev_info(&fd_hw_dev->pdev->dev, "-X. %s\n", __func__); + return 0; +} + +static int mtk_fd_suspend(struct device *dev) +{ + struct mtk_isp_fd_drv_data *fd_drv; + struct platform_device *pdev; + struct mtk_fd_drv_dev *fd_hw_dev; + + if (pm_runtime_suspended(dev)) + return 0; + + fd_drv = dev_get_drvdata(dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + pdev = fd_hw_dev->pdev; + + dev_info(&pdev->dev, "E.%s\n", __func__); + + if (atomic_read(&fd_hw_dev->fd_ctx.fd_user_cnt) > 0) { + mtk_fd_clk_ctrl(fd_hw_dev, clock_off); + dev_info(&pdev->dev, "Disable clock\n"); + } + + dev_info(&pdev->dev, "X.%s\n", __func__); + return 0; +} + +static int mtk_fd_resume(struct device *dev) +{ + struct mtk_isp_fd_drv_data *fd_drv; + struct platform_device *pdev; + struct mtk_fd_drv_dev *fd_hw_dev; + + if (pm_runtime_suspended(dev)) + return 0; + + fd_drv = dev_get_drvdata(dev); + fd_hw_dev = &fd_drv->fd_hw_dev; + pdev = fd_hw_dev->pdev; + + dev_info(&pdev->dev, "E.%s\n", __func__); + + if (atomic_read(&fd_hw_dev->fd_ctx.fd_user_cnt) > 0) { + mtk_fd_clk_ctrl(fd_hw_dev, clock_on); + dev_info(&pdev->dev, "Enable clock\n"); + } + + dev_info(&pdev->dev, "X.%s\n", __func__); + 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_suspend, mtk_fd_resume, NULL) +}; + +static struct platform_driver mtk_fd_driver = { + .probe = mtk_fd_probe, + .remove = mtk_fd_remove, + .driver = { + .name = FD_DRVNAME, + .of_match_table = 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/drivers/media/platform/mtk-isp/fd/mtk_fd.h b/drivers/media/platform/mtk-isp/fd/mtk_fd.h new file mode 100644 index 0000000..6cae440 --- /dev/null +++ b/drivers/media/platform/mtk-isp/fd/mtk_fd.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (C) 2015 MediaTek Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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 __MTK_FD_H__ +#define __MTK_FD_H__ + +#define MTK_FD_MAX_NO (1024) +#define MAX_FACE_SEL_NUM (MTK_FD_MAX_NO + 2) + +/* The max number of face sizes could be detected, for feature scaling */ +#define FACE_SIZE_NUM_MAX (14) + +/* FACE_SIZE_NUM_MAX + 1, first scale for input image W/H */ +#define FD_SCALE_NUM (15) + +/* Number of Learning data sets */ +#define LEARNDATA_NUM (18) + +struct fd_buffer { + __u64 va; /* used by APMCU access */ + __u32 pa; /* used by CM4 access */ + __u32 iova; /* used by HW access */ +} __packed; + +enum fd_img_format { + FMT_VYUY = 2, + FMT_UYVY, + FMT_YVYU, + FMT_YUYV, +}; + +enum fd_mode { + FDMODE, + SDMODE, + VFB, + CFB, +}; + +struct fd_face_result { + __u64 face_idx:12, type:1, x0:10, y0:10, x1:10, y1:10, + fcv:18, rip_dir:4, rop_dir:3, det_size:5; +}; + +struct fd_user_output { + struct fd_face_result face[MAX_FACE_SEL_NUM]; + __u16 face_number; +}; + +struct v4l2_fd_param { + u32 frame_id; + u16 src_img_h; + u16 src_img_w; + struct fd_buffer src_img; + struct fd_buffer fd_user_param; + struct fd_buffer fd_user_result; +} __packed; + +/** + * mtk_fd_enqueue - enqueue to fd driver + * + * @pdev: FD platform device + * @fdvtconfig: frame parameters from V4L2 common Framework + * + * Enqueue a frame to fd driver. + * + * Return: Return 0 if successfully, otherwise it is failed. + */ +int mtk_fd_enqueue(struct platform_device *pdev, + struct v4l2_fd_param *fd_param); + +/** + * mtk_fd_open - + * + * @pdev: FD platform device + * + * Open the FD device driver + * + * Return: Return 0 if success, otherwise it is failed. + */ +int mtk_fd_open(struct platform_device *pdev); + +/** + * mtk_fd_release - + * + * @pdev: FD platform device + * + * Enqueue a frame to FD driver. + * + * Return: Return 0 if success, otherwise it is failed. + */ +int mtk_fd_release(struct platform_device *pdev); + +/** + * mtk_fd_streamon - + * + * @pdev: FD platform device + * @id: device context id + * + * Stream on + * + * Return: Return 0 if success, otherwise it is failed. + */ +int mtk_fd_streamon(struct platform_device *pdev, u16 id); + +/** + * mtk_fd_streamoff - + * + * @pdev: FD platform device + * @id: device context id + * + * Stream off + * + * Return: Return 0 if success, otherwise it is failed. + */ +int mtk_fd_streamoff(struct platform_device *pdev, u16 id); + +#endif/*__MTK_FD_H__*/ -- 1.9.1