| /* |
| * Copyright 2018 NXP |
| */ |
| |
| /* |
| * The code contained herein is licensed under the GNU General Public |
| * License. You may obtain a copy of the GNU General Public License |
| * Version 2 or later at the following locations: |
| * |
| * http://www.opensource.org/licenses/gpl-license.html |
| * http://www.gnu.org/copyleft/gpl.html |
| */ |
| |
| /*! |
| * @file vpu_encoder_b0.c |
| * |
| * copyright here may be changed later |
| * |
| * |
| */ |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/module.h> |
| #include <linux/videodev2.h> |
| #include <linux/firmware.h> |
| #include <linux/interrupt.h> |
| #include <linux/file.h> |
| #include <linux/of_platform.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_device.h> |
| #include <linux/slab.h> |
| #include <linux/platform_data/dma-imx.h> |
| #include <linux/miscdevice.h> |
| #include <linux/fs.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/mx8_mu.h> |
| #include <linux/uaccess.h> |
| |
| #include <media/v4l2-ctrls.h> |
| #include <media/v4l2-device.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-ioctl.h> |
| #include <media/v4l2-mem2mem.h> |
| #include <media/videobuf2-v4l2.h> |
| #include <media/videobuf2-dma-contig.h> |
| #include <media/videobuf2-vmalloc.h> |
| |
| #include <soc/imx8/sc/ipc.h> |
| #include "vpu_encoder_b0.h" |
| #include "vpu_encoder_ctrl.h" |
| #include "vpu_encoder_config.h" |
| #include "vpu_event_msg.h" |
| #include "vpu_encoder_mem.h" |
| |
| #define VPU_ENC_DRIVER_VERSION "1.0.1" |
| |
| struct vpu_frame_info { |
| struct list_head list; |
| MEDIAIP_ENC_PIC_INFO info; |
| u32 bytesleft; |
| u32 wptr; |
| u32 rptr; |
| u32 start; |
| u32 end; |
| bool eos; |
| bool is_start; |
| unsigned long index; |
| struct queue_data *queue; |
| s64 timestamp; |
| }; |
| |
| unsigned int vpu_dbg_level_encoder = LVL_ERR | LVL_WARN | LVL_ALL; |
| static unsigned int reset_on_hang; |
| static unsigned int show_detail_index = VPU_DETAIL_INDEX_DFT; |
| static unsigned long debug_firmware_bitmap; |
| |
| #define ITEM_NAME(name) \ |
| [name] = #name |
| |
| static char *cmd2str[] = { |
| ITEM_NAME(GTB_ENC_CMD_NOOP), |
| ITEM_NAME(GTB_ENC_CMD_STREAM_START), |
| ITEM_NAME(GTB_ENC_CMD_FRAME_ENCODE), |
| ITEM_NAME(GTB_ENC_CMD_FRAME_SKIP), |
| ITEM_NAME(GTB_ENC_CMD_STREAM_STOP), |
| ITEM_NAME(GTB_ENC_CMD_PARAMETER_UPD), |
| ITEM_NAME(GTB_ENC_CMD_TERMINATE), |
| ITEM_NAME(GTB_ENC_CMD_SNAPSHOT), |
| ITEM_NAME(GTB_ENC_CMD_ROLL_SNAPSHOT), |
| ITEM_NAME(GTB_ENC_CMD_LOCK_SCHEDULER), |
| ITEM_NAME(GTB_ENC_CMD_UNLOCK_SCHEDULER), |
| ITEM_NAME(GTB_ENC_CMD_CONFIGURE_CODEC), |
| ITEM_NAME(GTB_ENC_CMD_DEAD_MARK), |
| ITEM_NAME(GTB_ENC_CMD_FIRM_RESET), |
| ITEM_NAME(GTB_ENC_CMD_RESERVED) |
| }; |
| |
| static char *event2str[] = { |
| ITEM_NAME(VID_API_EVENT_UNDEFINED), |
| ITEM_NAME(VID_API_ENC_EVENT_RESET_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_START_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_STOP_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_TERMINATE_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_FRAME_INPUT_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_FRAME_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_FRAME_RELEASE), |
| ITEM_NAME(VID_API_ENC_EVENT_PARA_UPD_DONE), |
| ITEM_NAME(VID_API_ENC_EVENT_MEM_REQUEST), |
| ITEM_NAME(VID_API_ENC_EVENT_FIRMWARE_XCPT), |
| ITEM_NAME(VID_API_ENC_EVENT_RESERVED) |
| }; |
| |
| static int wait_for_start_done(struct core_device *core, int resume); |
| static void wait_for_stop_done(struct vpu_ctx *ctx); |
| static int sw_reset_firmware(struct core_device *core, int resume); |
| static int enable_fps_sts(struct vpu_attr *attr); |
| static int disable_fps_sts(struct vpu_attr *attr); |
| static int configure_codec(struct vpu_ctx *ctx); |
| static struct vpu_frame_info *get_idle_frame(struct queue_data *queue); |
| static void put_frame_idle(struct vpu_frame_info *frame); |
| static int inc_frame(struct queue_data *queue); |
| static void dec_frame(struct vpu_frame_info *frame); |
| static int submit_input_and_encode(struct vpu_ctx *ctx); |
| static int process_stream_output(struct vpu_ctx *ctx); |
| |
| static char *get_event_str(u32 event) |
| { |
| if (event >= VID_API_ENC_EVENT_RESERVED) |
| return "UNKNOWN EVENT"; |
| return event2str[event]; |
| } |
| |
| static char *get_cmd_str(u32 cmdid) |
| { |
| if (cmdid >= GTB_ENC_CMD_RESERVED) |
| return "UNKNOWN CMD"; |
| return cmd2str[cmdid]; |
| } |
| |
| static void vpu_log_event(u_int32 uEvent, u_int32 ctxid) |
| { |
| if (uEvent >= VID_API_ENC_EVENT_RESERVED) |
| vpu_err("reveive event: 0x%X, ctx id:%d\n", |
| uEvent, ctxid); |
| else |
| vpu_dbg(LVL_EVT, "recevie event: %s, ctx id:%d\n", |
| event2str[uEvent], ctxid); |
| } |
| |
| static void vpu_log_cmd(u_int32 cmdid, u_int32 ctxid) |
| { |
| if (cmdid >= GTB_ENC_CMD_RESERVED) |
| vpu_err("send cmd: 0x%X, ctx id:%d\n", |
| cmdid, ctxid); |
| else |
| vpu_dbg(LVL_CMD, "send cmd: %s ctx id:%d\n", |
| cmd2str[cmdid], ctxid); |
| } |
| |
| static void count_event(struct vpu_ctx *ctx, u32 event) |
| { |
| struct vpu_attr *attr; |
| |
| WARN_ON(!ctx); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| if (event < VID_API_ENC_EVENT_RESERVED) |
| attr->statistic.event[event]++; |
| else |
| attr->statistic.event[VID_API_ENC_EVENT_RESERVED]++; |
| |
| attr->statistic.current_event = event; |
| getrawmonotonic(&attr->statistic.ts_event); |
| } |
| |
| static void count_cmd(struct vpu_attr *attr, u32 cmdid) |
| { |
| WARN_ON(!attr); |
| |
| if (cmdid < GTB_ENC_CMD_RESERVED) |
| attr->statistic.cmd[cmdid]++; |
| else |
| attr->statistic.cmd[GTB_ENC_CMD_RESERVED]++; |
| attr->statistic.current_cmd = cmdid; |
| getrawmonotonic(&attr->statistic.ts_cmd); |
| } |
| |
| static void count_yuv_input(struct vpu_ctx *ctx) |
| { |
| struct vpu_attr *attr = NULL; |
| |
| WARN_ON(!ctx); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| attr->statistic.yuv_count++; |
| } |
| |
| static void count_h264_output(struct vpu_ctx *ctx) |
| { |
| struct vpu_attr *attr = NULL; |
| |
| WARN_ON(!ctx); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| attr->statistic.h264_count++; |
| } |
| |
| static void count_encoded_frame(struct vpu_ctx *ctx) |
| { |
| struct vpu_attr *attr = NULL; |
| |
| WARN_ON(!ctx); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| attr->statistic.encoded_count++; |
| } |
| |
| static void count_timestamp_overwrite(struct vpu_ctx *ctx) |
| { |
| struct vpu_attr *attr = NULL; |
| |
| WARN_ON(!ctx); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| attr->statistic.timestamp_overwrite++; |
| } |
| |
| static void write_vpu_reg(struct vpu_dev *dev, u32 val, off_t reg) |
| { |
| writel(val, dev->regs_base + reg); |
| } |
| |
| static u32 read_vpu_reg(struct vpu_dev *dev, off_t reg) |
| { |
| return readl(dev->regs_base + reg); |
| } |
| |
| /* |
| * v4l2 ioctl() operation |
| * |
| */ |
| static struct vpu_v4l2_fmt formats_compressed_enc[] = { |
| { |
| .name = "H264 Encoded Stream", |
| .fourcc = V4L2_PIX_FMT_H264, |
| .num_planes = 1, |
| .venc_std = VPU_VIDEO_AVC, |
| .is_yuv = 0, |
| }, |
| }; |
| |
| static struct vpu_v4l2_fmt formats_yuv_enc[] = { |
| { |
| .name = "4:2:0 2 Planes Y/CbCr", |
| .fourcc = V4L2_PIX_FMT_NV12, |
| .num_planes = 2, |
| .venc_std = VPU_PF_YUV420_SEMIPLANAR, |
| .is_yuv = 1, |
| }, |
| }; |
| |
| static void vpu_ctx_send_cmd(struct vpu_ctx *ctx, uint32_t cmdid, |
| uint32_t cmdnum, uint32_t *local_cmddata); |
| |
| static void MU_sendMesgToFW(void __iomem *base, MSG_Type type, uint32_t value) |
| { |
| MU_SendMessage(base, 1, value); |
| MU_SendMessage(base, 0, type); |
| } |
| |
| #define GET_CTX_RPC(ctx, func) \ |
| func(&ctx->core_dev->shared_mem, ctx->str_index) |
| |
| pMEDIAIP_ENC_YUV_BUFFER_DESC get_rpc_yuv_buffer_desc(struct vpu_ctx *ctx) |
| { |
| return GET_CTX_RPC(ctx, rpc_get_yuv_buffer_desc); |
| } |
| |
| pBUFFER_DESCRIPTOR_TYPE get_rpc_stream_buffer_desc(struct vpu_ctx *ctx) |
| { |
| return GET_CTX_RPC(ctx, rpc_get_stream_buffer_desc); |
| } |
| |
| pMEDIAIP_ENC_EXPERT_MODE_PARAM get_rpc_expert_mode_param(struct vpu_ctx *ctx) |
| { |
| return GET_CTX_RPC(ctx, rpc_get_expert_mode_param); |
| } |
| |
| pMEDIAIP_ENC_PARAM get_rpc_enc_param(struct vpu_ctx *ctx) |
| { |
| return GET_CTX_RPC(ctx, rpc_get_enc_param); |
| } |
| |
| pMEDIAIP_ENC_MEM_POOL get_rpc_mem_pool(struct vpu_ctx *ctx) |
| { |
| return GET_CTX_RPC(ctx, rpc_get_mem_pool); |
| } |
| |
| pENC_ENCODING_STATUS get_rpc_encoding_status(struct vpu_ctx *ctx) |
| { |
| if (!ctx || !ctx->core_dev) |
| return NULL; |
| return GET_CTX_RPC(ctx, rpc_get_encoding_status); |
| } |
| |
| pENC_DSA_STATUS_t get_rpc_dsa_status(struct vpu_ctx *ctx) |
| { |
| if (!ctx || !ctx->core_dev) |
| return NULL; |
| return GET_CTX_RPC(ctx, rpc_get_dsa_status); |
| } |
| |
| static int vpu_enc_v4l2_ioctl_querycap(struct file *file, |
| void *fh, |
| struct v4l2_capability *cap) |
| { |
| vpu_log_func(); |
| strlcpy(cap->driver, "vpu encoder", sizeof(cap->driver)); |
| strlcpy(cap->card, "vpu encoder", sizeof(cap->card)); |
| strlcpy(cap->bus_info, "platform:", sizeof(cap->bus_info)); |
| cap->version = KERNEL_VERSION(0, 0, 1); |
| cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | |
| V4L2_CAP_STREAMING | |
| V4L2_CAP_VIDEO_CAPTURE_MPLANE | |
| V4L2_CAP_VIDEO_OUTPUT_MPLANE; |
| cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_enum_fmt_vid_cap_mplane(struct file *file, |
| void *fh, |
| struct v4l2_fmtdesc *f) |
| { |
| struct vpu_v4l2_fmt *fmt; |
| |
| vpu_log_func(); |
| if (f->index >= ARRAY_SIZE(formats_compressed_enc)) |
| return -EINVAL; |
| |
| fmt = &formats_compressed_enc[f->index]; |
| strlcpy(f->description, fmt->name, sizeof(f->description)); |
| f->pixelformat = fmt->fourcc; |
| f->flags |= V4L2_FMT_FLAG_COMPRESSED; |
| return 0; |
| } |
| static int vpu_enc_v4l2_ioctl_enum_fmt_vid_out_mplane(struct file *file, |
| void *fh, |
| struct v4l2_fmtdesc *f) |
| { |
| struct vpu_v4l2_fmt *fmt; |
| |
| vpu_log_func(); |
| if (f->index >= ARRAY_SIZE(formats_yuv_enc)) |
| return -EINVAL; |
| |
| fmt = &formats_yuv_enc[f->index]; |
| strlcpy(f->description, fmt->name, sizeof(f->description)); |
| f->pixelformat = fmt->fourcc; |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_enum_framesizes(struct file *file, void *fh, |
| struct v4l2_frmsizeenum *fsize) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct vpu_dev *vdev = ctx->dev; |
| |
| if (!fsize) |
| return -EINVAL; |
| |
| if (fsize->index) |
| return -EINVAL; |
| |
| if (!vdev) |
| return -EINVAL; |
| |
| vpu_log_func(); |
| fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; |
| fsize->stepwise.max_width = vdev->supported_size.max_width; |
| fsize->stepwise.max_height = vdev->supported_size.max_height; |
| fsize->stepwise.min_width = vdev->supported_size.min_width; |
| fsize->stepwise.min_height = vdev->supported_size.min_height; |
| fsize->stepwise.step_width = vdev->supported_size.step_width; |
| fsize->stepwise.step_height = vdev->supported_size.step_height; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_enum_frameintervals(struct file *file, void *fh, |
| struct v4l2_frmivalenum *fival) |
| { |
| u32 framerate; |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct vpu_dev *vdev = ctx->dev; |
| |
| |
| if (!fival) |
| return -EINVAL; |
| if (!vdev) |
| return -EINVAL; |
| |
| vpu_log_func(); |
| framerate = vdev->supported_fps.min + |
| fival->index * vdev->supported_fps.step; |
| if (framerate > vdev->supported_fps.max) |
| return -EINVAL; |
| |
| fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; |
| fival->discrete.numerator = 1; |
| fival->discrete.denominator = framerate; |
| |
| return 0; |
| } |
| |
| static struct queue_data *get_queue_by_v4l2_type(struct vpu_ctx *ctx, u32 type) |
| { |
| struct queue_data *queue = NULL; |
| |
| if (!ctx) |
| return NULL; |
| |
| switch (type) { |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| queue = &ctx->q_data[V4L2_SRC]; |
| break; |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| queue = &ctx->q_data[V4L2_DST]; |
| break; |
| default: |
| vpu_err("unsupport v4l2 buf type : %d\n", type); |
| break; |
| } |
| |
| return queue; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_g_fmt(struct file *file, |
| void *fh, |
| struct v4l2_format *f) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; |
| struct queue_data *q_data; |
| unsigned int i; |
| |
| q_data = get_queue_by_v4l2_type(ctx, f->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s\n", __func__, q_data->desc); |
| |
| if (!q_data->current_fmt) { |
| vpu_err("%s's current fmt is NULL\n", q_data->desc); |
| return -EINVAL; |
| } |
| |
| pix_mp->pixelformat = q_data->current_fmt->fourcc; |
| pix_mp->num_planes = q_data->current_fmt->num_planes; |
| pix_mp->width = q_data->width; |
| pix_mp->height = q_data->height; |
| pix_mp->field = V4L2_FIELD_ANY; |
| for (i = 0; i < pix_mp->num_planes; i++) |
| pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; |
| |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) |
| pix_mp->colorspace = V4L2_COLORSPACE_REC709; |
| else |
| pix_mp->plane_fmt[0].bytesperline = q_data->width; |
| |
| return 0; |
| } |
| |
| u32 cpu_phy_to_mu(struct core_device *dev, u32 addr) |
| { |
| return addr - dev->m0_p_fw_space_phy; |
| } |
| |
| static int initialize_enc_param(struct vpu_ctx *ctx) |
| { |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| pMEDIAIP_ENC_PARAM param = &attr->param; |
| |
| mutex_lock(&ctx->instance_mutex); |
| |
| param->eCodecMode = MEDIAIP_ENC_FMT_H264; |
| param->tEncMemDesc.uMemPhysAddr = 0; |
| param->tEncMemDesc.uMemVirtAddr = 0; |
| param->tEncMemDesc.uMemSize = 0; |
| param->uSrcStride = VPU_ENC_WIDTH_DEFAULT; |
| param->uSrcWidth = VPU_ENC_WIDTH_DEFAULT; |
| param->uSrcHeight = VPU_ENC_HEIGHT_DEFAULT; |
| param->uSrcOffset_x = 0; |
| param->uSrcOffset_y = 0; |
| param->uSrcCropWidth = VPU_ENC_WIDTH_DEFAULT; |
| param->uSrcCropHeight = VPU_ENC_HEIGHT_DEFAULT; |
| param->uOutWidth = VPU_ENC_WIDTH_DEFAULT; |
| param->uOutHeight = VPU_ENC_HEIGHT_DEFAULT; |
| param->uFrameRate = VPU_ENC_FRAMERATE_DEFAULT; |
| param->uMinBitRate = BITRATE_LOW_THRESHOLD; |
| |
| mutex_unlock(&ctx->instance_mutex); |
| |
| return 0; |
| } |
| |
| static int check_stepwise(u32 val, u32 min, u32 max, u32 step) |
| { |
| if (val < min) |
| return -EINVAL; |
| if (val > max) |
| return -EINVAL; |
| if ((val - min) % step) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int check_size(struct vpu_dev *vdev, u32 width, u32 height) |
| { |
| int ret; |
| |
| if (!vdev) |
| return -EINVAL; |
| |
| ret = check_stepwise(width, |
| vdev->supported_size.min_width, |
| vdev->supported_size.max_width, |
| vdev->supported_size.step_width); |
| if (ret) { |
| vpu_err("Unsupported frame size : %dx%d\n", width, height); |
| return -EINVAL; |
| } |
| ret = check_stepwise(height, |
| vdev->supported_size.min_height, |
| vdev->supported_size.max_height, |
| vdev->supported_size.step_height); |
| if (ret) { |
| vpu_err("Unsupported frame size : %dx%d\n", width, height); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int valid_crop_info(struct queue_data *queue, struct v4l2_rect *rect) |
| { |
| struct vpu_ctx *ctx; |
| u32 MIN_WIDTH; |
| u32 MIN_HEIGHT; |
| |
| if (!queue || !rect || !queue->ctx) |
| return -EINVAL; |
| |
| ctx = queue->ctx; |
| MIN_WIDTH = ctx->dev->supported_size.min_width; |
| MIN_HEIGHT = ctx->dev->supported_size.min_height; |
| |
| if (rect->left > queue->width - MIN_WIDTH || |
| rect->top > queue->height - MIN_HEIGHT) { |
| rect->left = 0; |
| rect->top = 0; |
| rect->width = queue->width; |
| rect->height = queue->height; |
| return 0; |
| } |
| |
| rect->width = min(rect->width, queue->width - rect->left); |
| if (rect->width) |
| rect->width = max_t(u32, rect->width, MIN_WIDTH); |
| else |
| rect->width = queue->width; |
| rect->height = min(rect->height, queue->height - rect->top); |
| if (rect->height) |
| rect->height = max_t(u32, rect->height, MIN_HEIGHT); |
| else |
| rect->height = queue->height; |
| |
| return 0; |
| } |
| |
| static int check_v4l2_fmt(struct vpu_dev *dev, struct v4l2_format *f) |
| { |
| int ret = -EINVAL; |
| |
| switch (f->type) { |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE: |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT: |
| ret = check_size(dev, f->fmt.pix.width, f->fmt.pix.height); |
| break; |
| case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: |
| case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: |
| ret = check_size(dev, f->fmt.pix_mp.width, |
| f->fmt.pix_mp.height); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static struct vpu_v4l2_fmt *find_fmt_by_fourcc(struct vpu_v4l2_fmt *fmts, |
| unsigned int size, |
| u32 fourcc) |
| { |
| unsigned int i; |
| |
| if (!fmts || !size) |
| return NULL; |
| |
| for (i = 0; i < size; i++) { |
| if (fmts[i].fourcc == fourcc) |
| return &fmts[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static char *cvrt_fourcc_to_str(u32 pixelformat) |
| { |
| static char str[5]; |
| |
| str[0] = pixelformat & 0xff; |
| str[1] = (pixelformat >> 8) & 0xff; |
| str[2] = (pixelformat >> 16) & 0xff; |
| str[3] = (pixelformat >> 24) & 0xff; |
| str[4] = '\0'; |
| |
| return str; |
| } |
| |
| static int set_yuv_queue_fmt(struct queue_data *q_data, struct v4l2_format *f) |
| { |
| struct vpu_v4l2_fmt *fmt = NULL; |
| struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; |
| int i; |
| |
| if (!q_data || !f) |
| return -EINVAL; |
| |
| fmt = find_fmt_by_fourcc(q_data->supported_fmts, q_data->fmt_count, |
| pix_mp->pixelformat); |
| if (!fmt) { |
| vpu_err("unsupport yuv fmt : %s\n", |
| cvrt_fourcc_to_str(pix_mp->pixelformat)); |
| return -EINVAL; |
| } |
| |
| q_data->width = pix_mp->width; |
| q_data->height = pix_mp->height; |
| q_data->rect.left = 0; |
| q_data->rect.top = 0; |
| q_data->rect.width = pix_mp->width; |
| q_data->rect.height = pix_mp->height; |
| q_data->sizeimage[0] = pix_mp->width * pix_mp->height; |
| q_data->sizeimage[1] = pix_mp->width * pix_mp->height / 2; |
| pix_mp->num_planes = fmt->num_planes; |
| for (i = 0; i < pix_mp->num_planes; i++) |
| pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; |
| |
| q_data->current_fmt = fmt; |
| |
| return 0; |
| } |
| |
| static u32 get_enc_minimum_sizeimage(u32 width, u32 height) |
| { |
| const u32 THRESHOLD = 256 * 1024; |
| u32 sizeimage; |
| |
| sizeimage = width * height / 2; |
| if (sizeimage < THRESHOLD) |
| sizeimage = THRESHOLD; |
| |
| return sizeimage; |
| } |
| |
| static int set_enc_queue_fmt(struct queue_data *q_data, struct v4l2_format *f) |
| { |
| struct vpu_v4l2_fmt *fmt = NULL; |
| struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; |
| u32 sizeimage; |
| |
| if (!q_data || !f) |
| return -EINVAL; |
| |
| fmt = find_fmt_by_fourcc(q_data->supported_fmts, q_data->fmt_count, |
| pix_mp->pixelformat); |
| if (!fmt) { |
| vpu_err("unsupport encode fmt : %s\n", |
| cvrt_fourcc_to_str(pix_mp->pixelformat)); |
| return -EINVAL; |
| } |
| |
| q_data->width = pix_mp->width; |
| q_data->height = pix_mp->height; |
| sizeimage = get_enc_minimum_sizeimage(pix_mp->width, pix_mp->height); |
| q_data->sizeimage[0] = max(sizeimage, pix_mp->plane_fmt[0].sizeimage); |
| pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage[0]; |
| |
| q_data->current_fmt = fmt; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_s_fmt(struct file *file, |
| void *fh, |
| struct v4l2_format *f) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| int ret = 0; |
| struct queue_data *q_data; |
| pMEDIAIP_ENC_PARAM pEncParam; |
| struct vpu_attr *attr; |
| |
| attr = get_vpu_ctx_attr(ctx); |
| pEncParam = &attr->param; |
| q_data = get_queue_by_v4l2_type(ctx, f->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| ret = check_v4l2_fmt(ctx->dev, f); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&ctx->instance_mutex); |
| if (V4L2_TYPE_IS_OUTPUT(f->type)) |
| ret = set_yuv_queue_fmt(q_data, f); |
| else |
| ret = set_enc_queue_fmt(q_data, f); |
| mutex_unlock(&ctx->instance_mutex); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_g_parm(struct file *file, void *fh, |
| struct v4l2_streamparm *parm) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct vpu_attr *attr = NULL; |
| pMEDIAIP_ENC_PARAM param = NULL; |
| |
| if (!parm || !ctx) |
| return -EINVAL; |
| |
| attr = get_vpu_ctx_attr(ctx); |
| param = &attr->param; |
| |
| vpu_log_func(); |
| parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; |
| parm->parm.capture.capturemode = V4L2_CAP_TIMEPERFRAME; |
| parm->parm.capture.timeperframe.numerator = 1; |
| parm->parm.capture.timeperframe.denominator = param->uFrameRate; |
| parm->parm.capture.readbuffers = 0; |
| |
| return 0; |
| } |
| |
| static int find_proper_framerate(struct vpu_dev *dev, struct v4l2_fract *fival) |
| { |
| u32 min_delta = INT_MAX; |
| struct v4l2_fract target_fival = {0, 0}; |
| u32 framerate; |
| |
| if (!fival || !dev) |
| return -EINVAL; |
| |
| framerate = dev->supported_fps.min; |
| |
| while (framerate <= dev->supported_fps.max) { |
| u32 delta; |
| |
| delta = abs(fival->numerator * framerate - |
| fival->denominator); |
| if (!delta) |
| return 0; |
| if (delta < min_delta) { |
| target_fival.numerator = 1; |
| target_fival.denominator = framerate; |
| min_delta = delta; |
| } |
| |
| framerate += dev->supported_fps.step; |
| } |
| if (!target_fival.numerator || !target_fival.denominator) |
| return -EINVAL; |
| |
| fival->numerator = target_fival.numerator; |
| fival->denominator = target_fival.denominator; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_s_parm(struct file *file, void *fh, |
| struct v4l2_streamparm *parm) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct vpu_attr *attr = NULL; |
| struct v4l2_fract fival; |
| int ret; |
| |
| if (!parm || !ctx) |
| return -EINVAL; |
| |
| vpu_log_func(); |
| attr = get_vpu_ctx_attr(ctx); |
| |
| fival.numerator = parm->parm.capture.timeperframe.numerator; |
| fival.denominator = parm->parm.capture.timeperframe.denominator; |
| if (!fival.numerator || !fival.denominator) |
| return -EINVAL; |
| |
| ret = find_proper_framerate(ctx->dev, &fival); |
| if (ret) { |
| vpu_err("Unsupported FPS : %d / %d\n", |
| fival.numerator, fival.denominator); |
| return ret; |
| } |
| |
| mutex_lock(&ctx->instance_mutex); |
| attr->param.uFrameRate = fival.denominator / fival.numerator; |
| mutex_unlock(&ctx->instance_mutex); |
| |
| parm->parm.capture.timeperframe.numerator = fival.numerator; |
| parm->parm.capture.timeperframe.denominator = fival.denominator; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_queue_expbuf(struct queue_data *queue, |
| struct v4l2_exportbuffer *buf) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_expbuf(&queue->vb2_q, buf); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_reqbufs(struct queue_data *queue, |
| struct v4l2_requestbuffers *reqbuf) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_reqbufs(&queue->vb2_q, reqbuf); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_querybuf(struct queue_data *queue, |
| struct v4l2_buffer *buf) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_querybuf(&queue->vb2_q, buf); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_qbuf(struct queue_data *queue, |
| struct v4l2_buffer *buf) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_qbuf(&queue->vb2_q, buf); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_dqbuf(struct queue_data *queue, |
| struct v4l2_buffer *buf, bool nonblocking) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_dqbuf(&queue->vb2_q, buf, nonblocking); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_enable(struct queue_data *queue, |
| enum v4l2_buf_type type) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_streamon(&queue->vb2_q, type); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static void clear_queue(struct queue_data *queue) |
| { |
| struct vpu_frame_info *frame; |
| struct vpu_frame_info *tmp; |
| struct vb2_data_req *p_data_req; |
| struct vb2_data_req *p_temp; |
| struct vb2_buffer *vb; |
| |
| if (!queue) |
| return; |
| |
| list_for_each_entry_safe(frame, tmp, &queue->frame_q, list) { |
| vpu_dbg(LVL_INFO, "drop frame\n"); |
| put_frame_idle(frame); |
| } |
| |
| list_for_each_entry_safe(frame, tmp, &queue->frame_idle, list) |
| dec_frame(frame); |
| |
| list_for_each_entry_safe(p_data_req, p_temp, &queue->drv_q, list) { |
| vpu_dbg(LVL_DEBUG, "%s(%d) - list_del(%p)\n", __func__, |
| p_data_req->sequence, p_data_req); |
| list_del(&p_data_req->list); |
| } |
| list_for_each_entry(vb, &queue->vb2_q.queued_list, queued_entry) { |
| if (vb->state == VB2_BUF_STATE_ACTIVE) |
| vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); |
| } |
| |
| INIT_LIST_HEAD(&queue->drv_q); |
| INIT_LIST_HEAD(&queue->frame_q); |
| INIT_LIST_HEAD(&queue->frame_idle); |
| } |
| |
| static int vpu_enc_queue_disable(struct queue_data *queue, |
| enum v4l2_buf_type type) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_streamoff(&queue->vb2_q, type); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_release(struct queue_data *queue) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) { |
| clear_queue(queue); |
| vb2_queue_release(&queue->vb2_q); |
| } |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_queue_mmap(struct queue_data *queue, |
| struct vm_area_struct *vma) |
| { |
| int ret = -EINVAL; |
| |
| down(&queue->drv_q_lock); |
| if (queue->vb2_q_inited) |
| ret = vb2_mmap(&queue->vb2_q, vma); |
| up(&queue->drv_q_lock); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_expbuf(struct file *file, |
| void *fh, |
| struct v4l2_exportbuffer *buf) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| |
| q_data = get_queue_by_v4l2_type(ctx, buf->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s\n", __func__, q_data->desc); |
| |
| return vpu_enc_queue_expbuf(q_data, buf); |
| } |
| |
| static int vpu_enc_v4l2_ioctl_subscribe_event(struct v4l2_fh *fh, |
| const struct v4l2_event_subscription *sub |
| ) |
| { |
| vpu_log_func(); |
| |
| switch (sub->type) { |
| case V4L2_EVENT_EOS: |
| return v4l2_event_subscribe(fh, sub, 0, NULL); |
| case V4L2_EVENT_SOURCE_CHANGE: |
| return v4l2_src_change_event_subscribe(fh, sub); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int vpu_enc_v4l2_ioctl_reqbufs(struct file *file, |
| void *fh, |
| struct v4l2_requestbuffers *reqbuf) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, reqbuf->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| ret = vpu_enc_queue_reqbufs(q_data, reqbuf); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_querybuf(struct file *file, |
| void *fh, |
| struct v4l2_buffer *buf) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| unsigned int i; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, buf->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| ret = vpu_enc_queue_querybuf(q_data, buf); |
| if (ret) |
| return ret; |
| |
| if (buf->memory == V4L2_MEMORY_MMAP) { |
| if (V4L2_TYPE_IS_MULTIPLANAR(buf->type)) { |
| for (i = 0; i < buf->length; i++) |
| buf->m.planes[i].m.mem_offset |= (q_data->type << MMAP_BUF_TYPE_SHIFT); |
| } else |
| buf->m.offset |= (q_data->type << MMAP_BUF_TYPE_SHIFT); |
| } |
| |
| return ret; |
| } |
| |
| static struct vb2_buffer *cvrt_v4l2_to_vb2_buffer(struct vb2_queue *vq, |
| struct v4l2_buffer *buf) |
| { |
| if (!vq || !buf) |
| return NULL; |
| |
| if (buf->index >= vq->num_buffers) |
| return NULL; |
| |
| return vq->bufs[buf->index]; |
| } |
| |
| static u32 get_v4l2_plane_payload(struct v4l2_plane *plane) |
| { |
| return plane->bytesused - plane->data_offset; |
| } |
| |
| static void set_v4l2_plane_payload(struct v4l2_plane *plane, u32 size) |
| { |
| plane->bytesused = plane->data_offset + size; |
| } |
| |
| static int is_valid_output_mplane_buf(struct queue_data *q_data, |
| struct vpu_v4l2_fmt *fmt, |
| struct v4l2_buffer *buf) |
| { |
| int i; |
| |
| for (i = 0; i < fmt->num_planes; i++) { |
| u32 bytesused = get_v4l2_plane_payload(&buf->m.planes[i]); |
| |
| if (!bytesused) { |
| set_v4l2_plane_payload(&buf->m.planes[i], |
| q_data->sizeimage[i]); |
| continue; |
| } |
| if (fmt->is_yuv && bytesused != q_data->sizeimage[i]) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int is_valid_output_buf(struct queue_data *q_data, |
| struct vpu_v4l2_fmt *fmt, |
| struct v4l2_buffer *buf) |
| { |
| if (!buf->bytesused) { |
| buf->bytesused = q_data->sizeimage[0]; |
| return 1; |
| } |
| if (fmt->is_yuv && buf->bytesused != q_data->sizeimage[0]) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int precheck_qbuf(struct queue_data *q_data, struct v4l2_buffer *buf) |
| { |
| struct vb2_buffer *vb = NULL; |
| struct vpu_v4l2_fmt *fmt; |
| int ret; |
| |
| if (!q_data || !buf) |
| return -EINVAL; |
| |
| if (!q_data->current_fmt) |
| return -EINVAL; |
| |
| vb = cvrt_v4l2_to_vb2_buffer(&q_data->vb2_q, buf); |
| if (!vb) { |
| vpu_err("invalid v4l2 buffer index:%d\n", buf->index); |
| return -EINVAL; |
| } |
| if (vb->state != VB2_BUF_STATE_DEQUEUED) { |
| vpu_err("invalid buffer state:%d\n", vb->state); |
| return -EINVAL; |
| } |
| |
| if (!V4L2_TYPE_IS_OUTPUT(buf->type)) |
| return 0; |
| |
| fmt = q_data->current_fmt; |
| if (V4L2_TYPE_IS_MULTIPLANAR(buf->type)) |
| ret = is_valid_output_mplane_buf(q_data, fmt, buf); |
| else |
| ret = is_valid_output_buf(q_data, fmt, buf); |
| if (!ret) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_qbuf(struct file *file, |
| void *fh, |
| struct v4l2_buffer *buf) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, buf->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| ret = precheck_qbuf(q_data, buf); |
| if (ret < 0) |
| return ret; |
| |
| ret = vpu_enc_queue_qbuf(q_data, buf); |
| if (ret) |
| return ret; |
| |
| if (V4L2_TYPE_IS_OUTPUT(buf->type)) { |
| mutex_lock(&ctx->dev->dev_mutex); |
| mutex_lock(&ctx->instance_mutex); |
| if (!test_bit(VPU_ENC_STATUS_CONFIGURED, &ctx->status)) |
| set_bit(VPU_ENC_STATUS_DATA_READY, &ctx->status); |
| configure_codec(ctx); |
| mutex_unlock(&ctx->instance_mutex); |
| mutex_unlock(&ctx->dev->dev_mutex); |
| |
| submit_input_and_encode(ctx); |
| count_yuv_input(ctx); |
| } else { |
| process_stream_output(ctx); |
| } |
| |
| return ret; |
| } |
| |
| static void notify_eos(struct vpu_ctx *ctx) |
| { |
| const struct v4l2_event ev = { |
| .type = V4L2_EVENT_EOS |
| }; |
| |
| mutex_lock(&ctx->instance_mutex); |
| if (!test_bit(VPU_ENC_STATUS_CLOSED, &ctx->status) && |
| !test_and_set_bit(VPU_ENC_STATUS_EOS_SEND, &ctx->status)) |
| v4l2_event_queue_fh(&ctx->fh, &ev); |
| mutex_unlock(&ctx->instance_mutex); |
| } |
| |
| static int send_eos(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return -EINVAL; |
| |
| if (!test_bit(VPU_ENC_STATUS_START_SEND, &ctx->status)) { |
| notify_eos(ctx); |
| return 0; |
| } |
| |
| if (!test_and_set_bit(VPU_ENC_STATUS_STOP_SEND, &ctx->status)) { |
| vpu_dbg(LVL_INFO, "stop stream\n"); |
| vpu_ctx_send_cmd(ctx, GTB_ENC_CMD_STREAM_STOP, 0, NULL); |
| } |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_dqbuf(struct file *file, |
| void *fh, |
| struct v4l2_buffer *buf) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, buf->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| ret = vpu_enc_queue_dqbuf(q_data, buf, file->f_flags & O_NONBLOCK); |
| |
| if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { |
| if (!ret) |
| count_h264_output(ctx); |
| buf->flags = q_data->vb2_reqs[buf->index].buffer_flags; |
| } |
| |
| return ret; |
| } |
| |
| static bool format_is_support(struct vpu_v4l2_fmt *format_table, |
| unsigned int table_size, |
| struct v4l2_format *f) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < table_size; i++) { |
| if (format_table[i].fourcc == f->fmt.pix_mp.pixelformat) |
| return true; |
| } |
| return false; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_try_fmt(struct file *file, |
| void *fh, |
| struct v4l2_format *f) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; |
| struct queue_data *q_data; |
| |
| q_data = get_queue_by_v4l2_type(ctx, f->type); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s\n", __func__, q_data->desc); |
| |
| if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| pix_mp->field = V4L2_FIELD_ANY; |
| pix_mp->colorspace = V4L2_COLORSPACE_REC709; |
| } |
| if (!format_is_support(q_data->supported_fmts, q_data->fmt_count, f)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_g_crop(struct file *file, void *fh, |
| struct v4l2_crop *cr) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *src = &ctx->q_data[V4L2_SRC]; |
| |
| if (!cr) |
| return -EINVAL; |
| |
| if (get_queue_by_v4l2_type(ctx, cr->type) != src) |
| return -EINVAL; |
| |
| vpu_log_func(); |
| cr->c.left = src->rect.left; |
| cr->c.top = src->rect.top; |
| cr->c.width = src->rect.width; |
| cr->c.height = src->rect.height; |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_s_crop(struct file *file, void *fh, |
| const struct v4l2_crop *cr) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *src = &ctx->q_data[V4L2_SRC]; |
| struct vpu_dev *dev = ctx->dev; |
| |
| if (!cr) |
| return -EINVAL; |
| if (!dev) |
| return -EINVAL; |
| |
| if (get_queue_by_v4l2_type(ctx, cr->type) != src) |
| return -EINVAL; |
| |
| vpu_log_func(); |
| src->rect.left = ALIGN(cr->c.left, dev->supported_size.step_width); |
| src->rect.top = ALIGN(cr->c.top, dev->supported_size.step_height); |
| src->rect.width = ALIGN(cr->c.width, dev->supported_size.step_width); |
| src->rect.height = ALIGN(cr->c.height, dev->supported_size.step_height); |
| valid_crop_info(src, &src->rect); |
| |
| return 0; |
| } |
| |
| static int response_stop_stream(struct vpu_ctx *ctx) |
| { |
| struct queue_data *queue; |
| |
| if (!ctx) |
| return -EINVAL; |
| |
| queue = &ctx->q_data[V4L2_SRC]; |
| |
| down(&queue->drv_q_lock); |
| if (!list_empty(&queue->drv_q)) |
| goto exit; |
| |
| if (!test_bit(VPU_ENC_FLAG_WRITEABLE, &queue->rw_flag)) |
| goto exit; |
| if (test_and_clear_bit(VPU_ENC_STATUS_STOP_REQ, &ctx->status)) |
| send_eos(ctx); |
| exit: |
| up(&queue->drv_q_lock); |
| |
| return 0; |
| } |
| |
| static int request_eos(struct vpu_ctx *ctx) |
| { |
| WARN_ON(!ctx); |
| |
| set_bit(VPU_ENC_STATUS_STOP_REQ, &ctx->status); |
| response_stop_stream(ctx); |
| |
| return 0; |
| } |
| |
| static int set_core_force_release(struct core_device *core) |
| { |
| int i; |
| |
| if (!core) |
| return -EINVAL; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (!core->ctx[i]) |
| continue; |
| set_bit(VPU_ENC_STATUS_FORCE_RELEASE, &core->ctx[i]->status); |
| } |
| |
| return 0; |
| } |
| |
| static void clear_start_status(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| clear_bit(VPU_ENC_STATUS_CONFIGURED, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_START_SEND, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_START_DONE, &ctx->status); |
| } |
| |
| static void clear_stop_status(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| clear_bit(VPU_ENC_STATUS_STOP_REQ, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_STOP_SEND, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_STOP_DONE, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_EOS_SEND, &ctx->status); |
| } |
| |
| static void reset_core_on_hang(struct core_device *core) |
| { |
| int ret; |
| int i; |
| |
| for (i = 0; i < core->supported_instance_count; i++) |
| clear_start_status(core->ctx[i]); |
| |
| ret = sw_reset_firmware(core, 1); |
| if (ret) |
| vpu_err("reset core[%d] on hang fail\n", core->id); |
| } |
| |
| static int set_core_hang(struct core_device *core) |
| { |
| core->hang = true; |
| |
| if (reset_on_hang) |
| reset_core_on_hang(core); |
| |
| return 0; |
| } |
| |
| static void clear_core_hang(struct core_device *core) |
| { |
| if (!core) |
| return; |
| |
| core->hang = false; |
| } |
| |
| static void wait_for_stop_done(struct vpu_ctx *ctx) |
| { |
| int ret; |
| |
| WARN_ON(!ctx); |
| |
| if (!test_bit(VPU_ENC_STATUS_START_SEND, &ctx->status)) |
| return; |
| if (test_bit(VPU_ENC_STATUS_STOP_DONE, &ctx->status)) |
| return; |
| |
| ret = wait_for_completion_timeout(&ctx->stop_cmp, |
| msecs_to_jiffies(500)); |
| if (!ret) |
| vpu_err("wait for stop done timeout\n"); |
| } |
| |
| static int vpu_enc_v4l2_ioctl_encoder_cmd(struct file *file, |
| void *fh, |
| struct v4l2_encoder_cmd *cmd |
| ) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| |
| vpu_dbg(LVL_FUNC, "%s(), cmd = %d, (%d, %d)\n", __func__, cmd->cmd, |
| ctx->core_dev->id, ctx->str_index); |
| switch (cmd->cmd) { |
| case V4L2_ENC_CMD_START: |
| break; |
| case V4L2_ENC_CMD_STOP: |
| request_eos(ctx); |
| break; |
| case V4L2_ENC_CMD_PAUSE: |
| break; |
| case V4L2_ENC_CMD_RESUME: |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_streamon(struct file *file, |
| void *fh, |
| enum v4l2_buf_type i |
| ) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct vpu_attr *attr; |
| struct queue_data *q_data; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, i); |
| if (!q_data) |
| return -EINVAL; |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (attr) { |
| attr->ts_start[V4L2_SRC] = 0; |
| attr->ts_start[V4L2_DST] = 0; |
| } |
| |
| ret = vpu_enc_queue_enable(q_data, i); |
| if (ret) |
| return ret; |
| |
| if (V4L2_TYPE_IS_OUTPUT(i)) { |
| mutex_lock(&ctx->dev->dev_mutex); |
| mutex_lock(&ctx->instance_mutex); |
| set_bit(VPU_ENC_STATUS_OUTPUT_READY, &ctx->status); |
| configure_codec(ctx); |
| mutex_unlock(&ctx->instance_mutex); |
| mutex_unlock(&ctx->dev->dev_mutex); |
| } |
| |
| return 0; |
| } |
| |
| static int vpu_enc_v4l2_ioctl_streamoff(struct file *file, |
| void *fh, |
| enum v4l2_buf_type i) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(fh); |
| struct queue_data *q_data; |
| int ret; |
| |
| q_data = get_queue_by_v4l2_type(ctx, i); |
| if (!q_data) |
| return -EINVAL; |
| |
| vpu_dbg(LVL_FUNC, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| request_eos(ctx); |
| wait_for_stop_done(ctx); |
| |
| ret = vpu_enc_queue_disable(q_data, i); |
| |
| return ret; |
| } |
| |
| static const struct v4l2_ioctl_ops vpu_enc_v4l2_ioctl_ops = { |
| .vidioc_querycap = vpu_enc_v4l2_ioctl_querycap, |
| .vidioc_enum_fmt_vid_cap_mplane = vpu_enc_v4l2_ioctl_enum_fmt_vid_cap_mplane, |
| .vidioc_enum_fmt_vid_out_mplane = vpu_enc_v4l2_ioctl_enum_fmt_vid_out_mplane, |
| .vidioc_enum_framesizes = vpu_enc_v4l2_ioctl_enum_framesizes, |
| .vidioc_enum_frameintervals = vpu_enc_v4l2_ioctl_enum_frameintervals, |
| .vidioc_g_fmt_vid_cap_mplane = vpu_enc_v4l2_ioctl_g_fmt, |
| .vidioc_g_fmt_vid_out_mplane = vpu_enc_v4l2_ioctl_g_fmt, |
| .vidioc_try_fmt_vid_cap_mplane = vpu_enc_v4l2_ioctl_try_fmt, |
| .vidioc_try_fmt_vid_out_mplane = vpu_enc_v4l2_ioctl_try_fmt, |
| .vidioc_s_fmt_vid_cap_mplane = vpu_enc_v4l2_ioctl_s_fmt, |
| .vidioc_s_fmt_vid_out_mplane = vpu_enc_v4l2_ioctl_s_fmt, |
| .vidioc_g_parm = vpu_enc_v4l2_ioctl_g_parm, |
| .vidioc_s_parm = vpu_enc_v4l2_ioctl_s_parm, |
| .vidioc_expbuf = vpu_enc_v4l2_ioctl_expbuf, |
| .vidioc_g_crop = vpu_enc_v4l2_ioctl_g_crop, |
| .vidioc_s_crop = vpu_enc_v4l2_ioctl_s_crop, |
| .vidioc_encoder_cmd = vpu_enc_v4l2_ioctl_encoder_cmd, |
| .vidioc_subscribe_event = vpu_enc_v4l2_ioctl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| .vidioc_reqbufs = vpu_enc_v4l2_ioctl_reqbufs, |
| .vidioc_querybuf = vpu_enc_v4l2_ioctl_querybuf, |
| .vidioc_qbuf = vpu_enc_v4l2_ioctl_qbuf, |
| .vidioc_dqbuf = vpu_enc_v4l2_ioctl_dqbuf, |
| .vidioc_streamon = vpu_enc_v4l2_ioctl_streamon, |
| .vidioc_streamoff = vpu_enc_v4l2_ioctl_streamoff, |
| }; |
| |
| static void vpu_core_send_cmd(struct core_device *core, u32 idx, |
| u32 cmdid, u32 cmdnum, u32 *local_cmddata) |
| { |
| WARN_ON(!core || idx >= VID_API_NUM_STREAMS); |
| |
| vpu_log_cmd(cmdid, idx); |
| count_cmd(&core->attr[idx], cmdid); |
| |
| mutex_lock(&core->cmd_mutex); |
| rpc_send_cmd_buf_encoder(&core->shared_mem, idx, |
| cmdid, cmdnum, local_cmddata); |
| mb(); |
| MU_SendMessage(core->mu_base_virtaddr, 0, COMMAND); |
| mutex_unlock(&core->cmd_mutex); |
| } |
| |
| static void vpu_ctx_send_cmd(struct vpu_ctx *ctx, uint32_t cmdid, |
| uint32_t cmdnum, uint32_t *local_cmddata) |
| { |
| vpu_core_send_cmd(ctx->core_dev, ctx->str_index, |
| cmdid, cmdnum, local_cmddata); |
| } |
| |
| static void set_core_fw_status(struct core_device *core, bool status) |
| { |
| core->fw_is_ready = status; |
| } |
| |
| static int reset_vpu_core_dev(struct core_device *core_dev) |
| { |
| if (!core_dev) |
| return -EINVAL; |
| |
| set_core_fw_status(core_dev, false); |
| core_dev->firmware_started = false; |
| |
| return 0; |
| } |
| |
| static int sw_reset_firmware(struct core_device *core, int resume) |
| { |
| int ret = 0; |
| |
| WARN_ON(!core); |
| |
| vpu_dbg(LVL_INFO, "core[%d] sw reset firmware\n", core->id); |
| |
| init_completion(&core->start_cmp); |
| vpu_core_send_cmd(core, 0, GTB_ENC_CMD_FIRM_RESET, 0, NULL); |
| ret = wait_for_start_done(core, resume); |
| if (ret) { |
| set_core_hang(core); |
| return -EINVAL; |
| } |
| core->reset_times++; |
| |
| return 0; |
| } |
| |
| static int process_core_hang(struct core_device *core) |
| { |
| int ret; |
| int i; |
| int instance_count = 0; |
| |
| if (!core->hang) |
| return 0; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (core->ctx[i]) |
| instance_count++; |
| } |
| |
| if (instance_count) |
| return -EBUSY; |
| |
| ret = sw_reset_firmware(core, 0); |
| if (ret) |
| return ret; |
| |
| clear_core_hang(core); |
| return 0; |
| } |
| |
| static void show_codec_configure(pMEDIAIP_ENC_PARAM param) |
| { |
| if (!param) |
| return; |
| |
| vpu_dbg(LVL_INFO, "Encoder Parameter:\n"); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Codec Mode", param->eCodecMode); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Profile", param->eProfile); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Level", param->uLevel); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Mem Phys Addr", param->tEncMemDesc.uMemPhysAddr); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Mem Virt Addr", param->tEncMemDesc.uMemVirtAddr); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Mem Size", param->tEncMemDesc.uMemSize); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Frame Rate", param->uFrameRate); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Stride", param->uSrcStride); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Width", param->uSrcWidth); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Height", param->uSrcHeight); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Offset x", param->uSrcOffset_x); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Offset y", param->uSrcOffset_y); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Crop Width", param->uSrcCropWidth); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Source Crop Height", param->uSrcCropHeight); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Out Width", param->uOutWidth); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Out Height", param->uOutHeight); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "I Frame Interval", param->uIFrameInterval); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "GOP Length", param->uGopBLength); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Low Latency Mode", param->uLowLatencyMode); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Bitrate Mode", param->eBitRateMode); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Target Bitrate", param->uTargetBitrate); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Min Bitrate", param->uMinBitRate); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "Max Bitrate", param->uMaxBitRate); |
| vpu_dbg(LVL_INFO, "\t%20s:%16d\n", |
| "QP", param->uInitSliceQP); |
| } |
| |
| static void show_firmware_version(struct core_device *core_dev, |
| unsigned int level) |
| { |
| pENC_RPC_HOST_IFACE pSharedInterface; |
| |
| if (!core_dev) |
| return; |
| |
| pSharedInterface = core_dev->shared_mem.pSharedInterface; |
| |
| vpu_dbg(level, "vpu encoder core[%d] firmware version is %d.%d.%d\n", |
| core_dev->id, |
| (pSharedInterface->FWVersion & 0x00ff0000) >> 16, |
| (pSharedInterface->FWVersion & 0x0000ff00) >> 8, |
| pSharedInterface->FWVersion & 0x000000ff); |
| } |
| |
| static void update_encode_size(struct vpu_ctx *ctx) |
| { |
| struct queue_data *src = NULL; |
| struct queue_data *dst = NULL; |
| struct vpu_attr *attr; |
| pMEDIAIP_ENC_PARAM pEncParam; |
| |
| if (!ctx) |
| return; |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return; |
| |
| src = &ctx->q_data[V4L2_SRC]; |
| dst = &ctx->q_data[V4L2_DST]; |
| pEncParam = &attr->param; |
| |
| pEncParam->uSrcStride = src->width; |
| pEncParam->uSrcWidth = src->width; |
| pEncParam->uSrcHeight = src->height; |
| pEncParam->uSrcOffset_x = src->rect.left; |
| pEncParam->uSrcOffset_y = src->rect.top; |
| pEncParam->uSrcCropWidth = src->rect.width; |
| pEncParam->uSrcCropHeight = src->rect.height; |
| pEncParam->uOutWidth = min(dst->width, src->rect.width); |
| pEncParam->uOutHeight = min(dst->height, src->rect.height); |
| } |
| |
| static void init_ctx_seq_info(struct vpu_ctx *ctx) |
| { |
| int i; |
| |
| if (!ctx) |
| return; |
| |
| ctx->sequence = 0; |
| for (i = 0; i < ARRAY_SIZE(ctx->timestams); i++) |
| ctx->timestams[i] = VPU_ENC_INVALID_TIMESTAMP; |
| } |
| |
| static void fill_ctx_seq(struct vpu_ctx *ctx, struct vb2_data_req *p_data_req) |
| { |
| u_int32 idx; |
| |
| WARN_ON(!ctx || !p_data_req); |
| |
| p_data_req->sequence = ctx->sequence++; |
| idx = p_data_req->sequence % VPU_ENC_SEQ_CAPACITY; |
| if (ctx->timestams[idx] != VPU_ENC_INVALID_TIMESTAMP) { |
| count_timestamp_overwrite(ctx); |
| vpu_dbg(LVL_FRAME, "[%d.%d][%d] overwrite timestamp\n", |
| ctx->core_dev->id, ctx->str_index, |
| p_data_req->sequence); |
| } |
| ctx->timestams[idx] = p_data_req->vb2_buf->timestamp; |
| } |
| |
| static s64 get_ctx_seq_timestamp(struct vpu_ctx *ctx, u32 sequence) |
| { |
| s64 timestamp; |
| u_int32 idx; |
| |
| WARN_ON(!ctx); |
| |
| idx = sequence % VPU_ENC_SEQ_CAPACITY; |
| timestamp = ctx->timestams[idx]; |
| ctx->timestams[idx] = VPU_ENC_INVALID_TIMESTAMP; |
| |
| return timestamp; |
| } |
| |
| static void fill_vb_sequence(struct vb2_buffer *vb, u32 sequence) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| |
| vbuf->sequence = sequence; |
| } |
| |
| static struct vb2_data_req *find_vb2_data_by_sequence(struct queue_data *queue, |
| u32 sequence) |
| { |
| int i; |
| |
| for (i = 0; i < queue->vb2_q.num_buffers; i++) { |
| if (!queue->vb2_reqs[i].vb2_buf) |
| continue; |
| if (queue->vb2_reqs[i].sequence == sequence) |
| return &queue->vb2_reqs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static int do_configure_codec(struct vpu_ctx *ctx) |
| { |
| pBUFFER_DESCRIPTOR_TYPE pEncStrBuffDesc = NULL; |
| pMEDIAIP_ENC_EXPERT_MODE_PARAM pEncExpertModeParam = NULL; |
| pMEDIAIP_ENC_PARAM enc_param; |
| struct vpu_attr *attr; |
| |
| if (!ctx || !ctx->core_dev) |
| return -EINVAL; |
| |
| attr = get_vpu_ctx_attr(ctx); |
| if (!attr) |
| return -EINVAL; |
| |
| if (vpu_enc_alloc_stream(ctx)) |
| return -ENOMEM; |
| |
| update_encode_size(ctx); |
| |
| enc_param = get_rpc_enc_param(ctx); |
| pEncStrBuffDesc = get_rpc_stream_buffer_desc(ctx); |
| |
| pEncStrBuffDesc->start = ctx->encoder_stream.phy_addr; |
| pEncStrBuffDesc->wptr = pEncStrBuffDesc->start; |
| pEncStrBuffDesc->rptr = pEncStrBuffDesc->start; |
| pEncStrBuffDesc->end = ctx->encoder_stream.phy_addr + |
| ctx->encoder_stream.size; |
| |
| vpu_dbg(LVL_DEBUG, |
| "pEncStrBuffDesc:start=%x, wptr=0x%x, rptr=%x, end=%x\n", |
| pEncStrBuffDesc->start, |
| pEncStrBuffDesc->wptr, |
| pEncStrBuffDesc->rptr, |
| pEncStrBuffDesc->end); |
| |
| pEncExpertModeParam = get_rpc_expert_mode_param(ctx); |
| pEncExpertModeParam->Calib.mem_chunk_phys_addr = 0; |
| pEncExpertModeParam->Calib.mem_chunk_virt_addr = 0; |
| pEncExpertModeParam->Calib.mem_chunk_size = 0; |
| pEncExpertModeParam->Calib.cb_base = ctx->encoder_stream.phy_addr; |
| pEncExpertModeParam->Calib.cb_size = ctx->encoder_stream.size; |
| |
| show_firmware_version(ctx->core_dev, LVL_INFO); |
| clear_stop_status(ctx); |
| memcpy(enc_param, &attr->param, sizeof(attr->param)); |
| vpu_ctx_send_cmd(ctx, GTB_ENC_CMD_CONFIGURE_CODEC, 0, NULL); |
| |
| show_codec_configure(enc_param); |
| |
| return 0; |
| } |
| |
| static int check_vpu_ctx_is_ready(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return false; |
| |
| if (!test_bit(VPU_ENC_STATUS_OUTPUT_READY, &ctx->status)) |
| return false; |
| if (!test_bit(VPU_ENC_STATUS_DATA_READY, &ctx->status)) |
| return false; |
| |
| return true; |
| } |
| |
| static int configure_codec(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return -EINVAL; |
| |
| if (!check_vpu_ctx_is_ready(ctx)) |
| return 0; |
| |
| if (ctx->core_dev->snapshot) |
| return 0; |
| |
| if (test_bit(VPU_ENC_STATUS_SNAPSHOT, &ctx->status)) |
| return 0; |
| |
| if (!test_and_set_bit(VPU_ENC_STATUS_CONFIGURED, &ctx->status)) { |
| do_configure_codec(ctx); |
| clear_bit(VPU_ENC_STATUS_OUTPUT_READY, &ctx->status); |
| clear_bit(VPU_ENC_STATUS_DATA_READY, &ctx->status); |
| } |
| |
| return 0; |
| } |
| |
| static void dump_vb2_data(struct vb2_buffer *vb) |
| { |
| #ifdef DUMP_DATA |
| const int DATA_NUM = 10; |
| char *read_data; |
| u_int32 read_idx; |
| char data_str[1024]; |
| int num = 0; |
| |
| if (!vb) |
| return; |
| |
| read_data = vb2_plane_vaddr(vb, 0); |
| num = scnprintf(data_str, sizeof(data_str), |
| "transfer data from virt 0x%p: ", read_data); |
| for (read_idx = 0; read_idx < DATA_NUM; read_idx++) |
| num += scnprintf(data_str + num, sizeof(data_str) - num, |
| " 0x%x", read_data[read_idx]); |
| |
| vpu_dbg(LVL_DEBUG, "%s\n", data_str); |
| #endif |
| } |
| |
| static u32 get_vb2_plane_phy_addr(struct vb2_buffer *vb, unsigned int plane_no) |
| { |
| dma_addr_t *dma_addr; |
| |
| dma_addr = vb2_plane_cookie(vb, plane_no); |
| return *dma_addr + vb->planes[plane_no].data_offset; |
| } |
| |
| static void record_start_time(struct vpu_ctx *ctx, enum QUEUE_TYPE type) |
| { |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| struct timespec ts; |
| |
| if (!attr) |
| return; |
| |
| if (attr->ts_start[type]) |
| return; |
| |
| getrawmonotonic(&ts); |
| attr->ts_start[type] = ts.tv_sec * MSEC_PER_SEC + |
| ts.tv_nsec / NSEC_PER_MSEC; |
| } |
| |
| static bool update_yuv_addr(struct vpu_ctx *ctx) |
| { |
| bool bGotAFrame = FALSE; |
| |
| struct vb2_data_req *p_data_req; |
| struct queue_data *This = &ctx->q_data[V4L2_SRC]; |
| |
| pMEDIAIP_ENC_YUV_BUFFER_DESC desc; |
| |
| desc = get_rpc_yuv_buffer_desc(ctx); |
| |
| if (list_empty(&This->drv_q)) |
| return bGotAFrame; |
| |
| p_data_req = list_first_entry(&This->drv_q, typeof(*p_data_req), list); |
| |
| dump_vb2_data(p_data_req->vb2_buf); |
| |
| desc->uLumaBase = get_vb2_plane_phy_addr(p_data_req->vb2_buf, 0); |
| desc->uChromaBase = get_vb2_plane_phy_addr(p_data_req->vb2_buf, 1); |
| |
| if (desc->uLumaBase != 0) |
| bGotAFrame = TRUE; |
| |
| /* |
| * keeps increasing, |
| * so just a frame input count rather than a Frame buffer ID |
| */ |
| desc->uFrameID = p_data_req->sequence; |
| if (test_and_clear_bit(VPU_ENC_STATUS_KEY_FRAME, &ctx->status)) |
| desc->uKeyFrame = 1; |
| else |
| desc->uKeyFrame = 0; |
| list_del(&p_data_req->list); |
| |
| return bGotAFrame; |
| } |
| |
| static void get_kmp_next(const u8 *p, int *next, int size) |
| { |
| int k = -1; |
| int j = 0; |
| |
| next[0] = -1; |
| while (j < size - 1) { |
| if (k == -1 || p[j] == p[k]) { |
| ++k; |
| ++j; |
| next[j] = k; |
| } else { |
| k = next[k]; |
| } |
| } |
| } |
| |
| static int kmp_serach(u8 *s, int s_len, const u8 *p, int p_len, int *next) |
| { |
| int i = 0; |
| int j = 0; |
| |
| while (i < s_len && j < p_len) { |
| if (j == -1 || s[i] == p[j]) { |
| i++; |
| j++; |
| } else { |
| j = next[j]; |
| } |
| } |
| if (j == p_len) |
| return i - j; |
| else |
| return -1; |
| } |
| |
| static int get_stuff_data_size(u8 *data, int size) |
| { |
| const u8 pattern[] = VPU_STRM_END_PATTERN; |
| int next[] = VPU_STRM_END_PATTERN; |
| int index; |
| |
| if (size < ARRAY_SIZE(pattern)) |
| return 0; |
| |
| get_kmp_next(pattern, next, ARRAY_SIZE(pattern)); |
| index = kmp_serach(data, size, pattern, ARRAY_SIZE(pattern), next); |
| if (index < 0) |
| return 0; |
| vpu_dbg(LVL_DEBUG, "find end_of_stream nal\n"); |
| return size - index; |
| } |
| |
| static void count_strip_info(struct vpu_strip_info *info, u32 bytes) |
| { |
| if (!info) |
| return; |
| |
| info->count++; |
| info->total += bytes; |
| if (info->max < bytes) |
| info->max = bytes; |
| } |
| |
| static void strip_stuff_data_on_tail(struct vpu_ctx *ctx, struct vb2_buffer *vb) |
| { |
| u8 *ptr = vb2_plane_vaddr(vb, 0); |
| unsigned long bytesused = vb2_get_plane_payload(vb, 0); |
| int count = VPU_TAIL_SERACH_SIZE; |
| int stuff_size; |
| |
| if (count > bytesused) |
| count = bytesused; |
| |
| if (!count) |
| return; |
| |
| stuff_size = get_stuff_data_size(ptr + bytesused - count, count); |
| if (stuff_size) { |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| |
| if (attr) |
| count_strip_info(&attr->statistic.strip_sts.eos, |
| stuff_size); |
| |
| vpu_dbg(LVL_DEBUG, "strip %d bytes stuff data\n", stuff_size); |
| vb2_set_plane_payload(vb, 0, bytesused - stuff_size); |
| } |
| } |
| |
| static int check_enc_rw_flag(int flag) |
| { |
| int ret = -EINVAL; |
| |
| switch (flag) { |
| case VPU_ENC_FLAG_WRITEABLE: |
| case VPU_ENC_FLAG_READABLE: |
| ret = 0; |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void set_queue_rw_flag(struct queue_data *queue, int flag) |
| { |
| if (!queue) |
| return; |
| |
| if (check_enc_rw_flag(flag)) |
| return; |
| |
| set_bit(flag, &queue->rw_flag); |
| } |
| |
| static void clear_queue_rw_flag(struct queue_data *queue, int flag) |
| { |
| if (!queue) |
| return; |
| |
| if (check_enc_rw_flag(flag)) |
| return; |
| |
| clear_bit(flag, &queue->rw_flag); |
| } |
| |
| static int submit_input_and_encode(struct vpu_ctx *ctx) |
| { |
| struct queue_data *queue; |
| |
| if (!ctx) |
| return -EINVAL; |
| |
| queue = &ctx->q_data[V4L2_SRC]; |
| |
| down(&queue->drv_q_lock); |
| |
| if (!test_bit(VPU_ENC_FLAG_WRITEABLE, &queue->rw_flag)) |
| goto exit; |
| |
| if (list_empty(&queue->drv_q)) |
| goto exit; |
| |
| if (test_bit(VPU_ENC_STATUS_STOP_SEND, &ctx->status)) |
| goto exit; |
| if (!test_bit(VPU_ENC_STATUS_START_DONE, &ctx->status)) |
| goto exit; |
| |
| if (update_yuv_addr(ctx)) { |
| vpu_ctx_send_cmd(ctx, GTB_ENC_CMD_FRAME_ENCODE, 0, NULL); |
| clear_queue_rw_flag(queue, VPU_ENC_FLAG_WRITEABLE); |
| record_start_time(ctx, V4L2_SRC); |
| } |
| exit: |
| up(&queue->drv_q_lock); |
| |
| return 0; |
| } |
| |
| static void add_rptr(struct vpu_frame_info *frame, u32 length) |
| { |
| WARN_ON(!frame); |
| |
| frame->rptr += length; |
| if (frame->rptr >= frame->end) |
| frame->rptr -= (frame->end - frame->start); |
| } |
| |
| static void report_frame_type(struct vb2_data_req *p_data_req, |
| struct vpu_frame_info *frame) |
| { |
| WARN_ON(!p_data_req || !frame); |
| |
| switch (frame->info.ePicType) { |
| case MEDIAIP_ENC_PIC_TYPE_IDR_FRAME: |
| case MEDIAIP_ENC_PIC_TYPE_I_FRAME: |
| p_data_req->buffer_flags = V4L2_BUF_FLAG_KEYFRAME; |
| break; |
| case MEDIAIP_ENC_PIC_TYPE_P_FRAME: |
| p_data_req->buffer_flags = V4L2_BUF_FLAG_PFRAME; |
| break; |
| case MEDIAIP_ENC_PIC_TYPE_B_FRAME: |
| p_data_req->buffer_flags = V4L2_BUF_FLAG_BFRAME; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static u32 calc_frame_length(struct vpu_frame_info *frame) |
| { |
| u32 length; |
| u32 buffer_size; |
| |
| WARN_ON(!frame); |
| |
| if (frame->eos) |
| return 0; |
| |
| buffer_size = frame->end - frame->start; |
| if (!buffer_size) |
| return 0; |
| |
| length = (buffer_size + frame->wptr - frame->rptr) % buffer_size; |
| |
| return length; |
| } |
| |
| static u32 get_ptr(u32 ptr) |
| { |
| return (ptr | 0x80000000); |
| } |
| |
| static void *get_rptr_virt(struct vpu_ctx *ctx, struct vpu_frame_info *frame) |
| { |
| WARN_ON(!ctx || !frame); |
| |
| return ctx->encoder_stream.virt_addr + frame->rptr - frame->start; |
| } |
| |
| static int find_nal_begin(u8 *data, u32 size) |
| { |
| const u8 pattern[] = VPU_STRM_BEGIN_PATTERN; |
| int next[] = VPU_STRM_BEGIN_PATTERN; |
| u32 len; |
| int index; |
| |
| len = ARRAY_SIZE(pattern); |
| get_kmp_next(pattern, next, len); |
| index = kmp_serach(data, size, pattern, len, next); |
| if (index > 0 && data[index - 1] == 0) |
| index--; |
| |
| return index; |
| } |
| |
| static int find_frame_start_and_skip(struct vpu_ctx *ctx, |
| struct vpu_frame_info *frame, int skip) |
| { |
| u32 length; |
| u32 bytesskiped = 0; |
| u8 *data = get_rptr_virt(ctx, frame); |
| int index; |
| |
| length = frame->bytesleft; |
| if (frame->rptr + length <= frame->end) { |
| index = find_nal_begin(data, length); |
| if (index >= 0) |
| bytesskiped += index; |
| else |
| bytesskiped += length; |
| } else { |
| u32 size = frame->end - frame->rptr; |
| |
| index = find_nal_begin(data, size); |
| if (index >= 0) { |
| bytesskiped += index; |
| } else { |
| bytesskiped += size; |
| |
| data = ctx->encoder_stream.virt_addr; |
| size = length - size; |
| index = find_nal_begin(data, size); |
| if (index >= 0) |
| bytesskiped += index; |
| else |
| bytesskiped += size; |
| } |
| } |
| |
| if (skip && bytesskiped) { |
| add_rptr(frame, bytesskiped); |
| frame->bytesleft -= bytesskiped; |
| } |
| |
| return bytesskiped; |
| } |
| |
| static int transfer_stream_output(struct vpu_ctx *ctx, |
| struct vpu_frame_info *frame, |
| struct vb2_data_req *p_data_req) |
| { |
| struct vb2_buffer *vb = NULL; |
| u32 length; |
| void *pdst; |
| |
| WARN_ON(!ctx || !frame || !p_data_req); |
| |
| length = frame->bytesleft; |
| |
| vb = p_data_req->vb2_buf; |
| if (length > vb->planes[0].length) |
| length = vb->planes[0].length; |
| vb2_set_plane_payload(vb, 0, length); |
| |
| pdst = vb2_plane_vaddr(vb, 0); |
| if (frame->rptr + length <= frame->end) { |
| memcpy(pdst, get_rptr_virt(ctx, frame), length); |
| frame->bytesleft -= length; |
| add_rptr(frame, length); |
| } else { |
| u32 offset = frame->end - frame->rptr; |
| |
| memcpy(pdst, get_rptr_virt(ctx, frame), offset); |
| frame->bytesleft -= offset; |
| add_rptr(frame, offset); |
| length -= offset; |
| memcpy(pdst + offset, get_rptr_virt(ctx, frame), length); |
| frame->bytesleft -= length; |
| add_rptr(frame, length); |
| } |
| report_frame_type(p_data_req, frame); |
| if (frame->bytesleft) |
| return 0; |
| |
| strip_stuff_data_on_tail(ctx, p_data_req->vb2_buf); |
| |
| return 0; |
| } |
| |
| static int append_empty_end_frame(struct vb2_data_req *p_data_req) |
| { |
| struct vb2_buffer *vb = NULL; |
| const u8 pattern[] = VPU_STRM_END_PATTERN; |
| void *pdst; |
| |
| WARN_ON(!p_data_req); |
| |
| vb = p_data_req->vb2_buf; |
| pdst = vb2_plane_vaddr(vb, 0); |
| memcpy(pdst, pattern, ARRAY_SIZE(pattern)); |
| |
| vb2_set_plane_payload(vb, 0, ARRAY_SIZE(pattern)); |
| p_data_req->buffer_flags = V4L2_BUF_FLAG_LAST; |
| |
| vpu_dbg(LVL_INFO, "append the last frame\n"); |
| |
| return 0; |
| } |
| |
| static bool is_valid_frame_read_pos(u32 ptr, struct vpu_frame_info *frame) |
| { |
| if (ptr < frame->start || ptr >= frame->end) |
| return false; |
| if (ptr >= frame->rptr && ptr < frame->wptr) |
| return true; |
| if (frame->rptr > frame->wptr) { |
| if (ptr >= frame->rptr || ptr < frame->wptr) |
| return true; |
| } |
| |
| return false; |
| } |
| static int precheck_frame(struct vpu_ctx *ctx, struct vpu_frame_info *frame) |
| { |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| u32 length; |
| int bytesskiped; |
| u32 rptr; |
| |
| if (frame->eos) |
| return 0; |
| if (!frame->is_start) |
| return 0; |
| |
| add_rptr(frame, 0); |
| frame->is_start = false; |
| rptr = get_ptr(frame->info.uStrBuffWrPtr); |
| if (rptr == frame->end) |
| rptr = frame->start; |
| |
| if (is_valid_frame_read_pos(rptr, frame)) { |
| if (rptr != frame->rptr) { |
| vpu_dbg(LVL_DEBUG, "frame skip %d bytes\n", |
| rptr - frame->rptr); |
| count_strip_info(&attr->statistic.strip_sts.fw, |
| rptr - frame->rptr); |
| } |
| frame->rptr = rptr; |
| } else { |
| vpu_err("[%ld]wrong uStrBuffWrPtr:0x%x\n", frame->index, rptr); |
| } |
| |
| length = calc_frame_length(frame); |
| if (!length || length < frame->bytesleft) { |
| vpu_err("[%d][%d]'s frame[%ld] invalid, want %d but %d, drop\n", |
| ctx->core_dev->id, ctx->str_index, |
| frame->index, frame->bytesleft, length); |
| vpu_err("uStrBuffWrPtr = 0x%x, uFrameSize = 0x%x\n", |
| frame->info.uStrBuffWrPtr, frame->info.uFrameSize); |
| add_rptr(frame, length); |
| return -EINVAL; |
| } |
| |
| bytesskiped = find_frame_start_and_skip(ctx, frame, 0); |
| if (!bytesskiped) |
| return 0; |
| |
| if (attr) |
| count_strip_info(&attr->statistic.strip_sts.begin, bytesskiped); |
| |
| return 0; |
| } |
| |
| static int inc_frame(struct queue_data *queue) |
| { |
| struct vpu_frame_info *frame = NULL; |
| |
| if (!queue) |
| return -EINVAL; |
| |
| frame = vzalloc(sizeof(*frame)); |
| if (!frame) |
| return -EINVAL; |
| |
| frame->queue = queue; |
| list_add_tail(&frame->list, &queue->frame_idle); |
| atomic64_inc(&queue->frame_count); |
| |
| vpu_dbg(LVL_DEBUG, "++ frame : %ld\n", |
| atomic64_read(&queue->frame_count)); |
| |
| return 0; |
| } |
| |
| static void dec_frame(struct vpu_frame_info *frame) |
| { |
| if (!frame) |
| return; |
| list_del_init(&frame->list); |
| if (frame->queue) { |
| atomic64_dec(&frame->queue->frame_count); |
| |
| vpu_dbg(LVL_DEBUG, "-- frame : %ld\n", |
| atomic64_read(&frame->queue->frame_count)); |
| } |
| VPU_SAFE_RELEASE(frame, vfree); |
| } |
| |
| static struct vpu_frame_info *get_idle_frame(struct queue_data *queue) |
| { |
| struct vpu_frame_info *frame = NULL; |
| |
| if (!queue) |
| return NULL; |
| |
| if (list_empty(&queue->frame_idle)) |
| inc_frame(queue); |
| frame = list_first_entry(&queue->frame_idle, |
| struct vpu_frame_info, list); |
| if (frame) |
| list_del_init(&frame->list); |
| |
| return frame; |
| } |
| |
| static void put_frame_idle(struct vpu_frame_info *frame) |
| { |
| struct queue_data *queue; |
| |
| if (!frame) |
| return; |
| |
| list_del_init(&frame->list); |
| memset(&frame->info, 0, sizeof(frame->info)); |
| frame->bytesleft = 0; |
| frame->wptr = 0; |
| frame->rptr = 0; |
| frame->start = 0; |
| frame->end = 0; |
| frame->eos = false; |
| frame->is_start = false; |
| frame->index = 0; |
| frame->timestamp = VPU_ENC_INVALID_TIMESTAMP; |
| queue = frame->queue; |
| if (queue && atomic64_read(&queue->frame_count) <= FRAME_COUNT_THD) |
| list_add_tail(&frame->list, &queue->frame_idle); |
| else |
| dec_frame(frame); |
| } |
| |
| static bool process_frame_done(struct queue_data *queue) |
| { |
| struct vpu_ctx *ctx; |
| struct vb2_data_req *p_data_req = NULL; |
| struct vpu_frame_info *frame = NULL; |
| pBUFFER_DESCRIPTOR_TYPE stream_buffer_desc; |
| |
| WARN_ON(!queue || !queue->ctx); |
| |
| ctx = queue->ctx; |
| |
| stream_buffer_desc = get_rpc_stream_buffer_desc(ctx); |
| |
| if (list_empty(&queue->frame_q)) |
| return false; |
| |
| frame = list_first_entry(&queue->frame_q, typeof(*frame), list); |
| if (!frame) |
| return false; |
| |
| frame->rptr = get_ptr(stream_buffer_desc->rptr); |
| |
| if (precheck_frame(ctx, frame)) { |
| stream_buffer_desc->rptr = frame->rptr; |
| put_frame_idle(frame); |
| frame = NULL; |
| return true; |
| } |
| |
| if (list_empty(&queue->drv_q)) |
| return false; |
| |
| p_data_req = list_first_entry(&queue->drv_q, typeof(*p_data_req), list); |
| |
| if (frame->eos) |
| append_empty_end_frame(p_data_req); |
| else |
| transfer_stream_output(ctx, frame, p_data_req); |
| |
| stream_buffer_desc->rptr = frame->rptr; |
| if (!frame->eos) { |
| fill_vb_sequence(p_data_req->vb2_buf, frame->info.uFrameID); |
| p_data_req->vb2_buf->timestamp = frame->timestamp; |
| } |
| if (!frame->bytesleft) { |
| put_frame_idle(frame); |
| frame = NULL; |
| } |
| list_del(&p_data_req->list); |
| |
| if (p_data_req->vb2_buf->state == VB2_BUF_STATE_ACTIVE) |
| vb2_buffer_done(p_data_req->vb2_buf, VB2_BUF_STATE_DONE); |
| else |
| vpu_err("vb2_buf's state is invalid(%d\n)", |
| p_data_req->vb2_buf->state); |
| |
| return true; |
| } |
| |
| static int process_stream_output(struct vpu_ctx *ctx) |
| { |
| struct queue_data *queue = NULL; |
| |
| if (!ctx) |
| return -EINVAL; |
| |
| queue = &ctx->q_data[V4L2_DST]; |
| |
| down(&queue->drv_q_lock); |
| while (1) { |
| if (!process_frame_done(queue)) |
| break; |
| } |
| up(&queue->drv_q_lock); |
| |
| return 0; |
| } |
| |
| static void show_enc_pic_info(MEDIAIP_ENC_PIC_INFO *pEncPicInfo) |
| { |
| #ifdef TB_REC_DBG |
| vpu_dbg(LVL_DEBUG, " - Frame ID : 0x%x\n", |
| pEncPicInfo->uFrameID); |
| |
| switch (pEncPicInfo->ePicType) { |
| case MEDIAIP_ENC_PIC_TYPE_IDR_FRAME: |
| vpu_dbg(LVL_DEBUG, " - Picture Type : IDR picture\n"); |
| break; |
| case MEDIAIP_ENC_PIC_TYPE_I_FRAME: |
| vpu_dbg(LVL_DEBUG, " - Picture Type : I picture\n"); |
| break; |
| case MEDIAIP_ENC_PIC_TYPE_P_FRAME: |
| vpu_dbg(LVL_DEBUG, " - Picture Type : P picture\n"); |
| break; |
| case MEDIAIP_ENC_PIC_TYPE_B_FRAME: |
| vpu_dbg(LVL_DEBUG, " - Picture Type : B picture\n"); |
| break; |
| default: |
| vpu_dbg(LVL_DEBUG, " - Picture Type : BI picture\n"); |
| break; |
| } |
| vpu_dbg(LVL_DEBUG, " - Skipped frame : 0x%x\n", |
| pEncPicInfo->uSkippedFrame); |
| vpu_dbg(LVL_DEBUG, " - Frame size : 0x%x\n", |
| pEncPicInfo->uFrameSize); |
| vpu_dbg(LVL_DEBUG, " - Frame CRC : 0x%x\n", |
| pEncPicInfo->uFrameCrc); |
| #endif |
| } |
| |
| static int handle_event_start_done(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return -EINVAL; |
| |
| set_bit(VPU_ENC_STATUS_START_DONE, &ctx->status); |
| set_queue_rw_flag(&ctx->q_data[V4L2_SRC], VPU_ENC_FLAG_WRITEABLE); |
| submit_input_and_encode(ctx); |
| |
| enable_fps_sts(get_vpu_ctx_attr(ctx)); |
| |
| return 0; |
| } |
| |
| static int handle_event_mem_request(struct vpu_ctx *ctx, |
| MEDIAIP_ENC_MEM_REQ_DATA *req_data) |
| { |
| int ret; |
| |
| if (!ctx || !req_data) |
| return -EINVAL; |
| |
| ret = vpu_enc_alloc_mem(ctx, req_data, get_rpc_mem_pool(ctx)); |
| if (ret) { |
| vpu_err("fail to alloc encoder memory\n"); |
| return ret; |
| } |
| vpu_ctx_send_cmd(ctx, GTB_ENC_CMD_STREAM_START, 0, NULL); |
| set_bit(VPU_ENC_STATUS_START_SEND, &ctx->status); |
| |
| return 0; |
| } |
| |
| static int handle_event_frame_done(struct vpu_ctx *ctx, |
| MEDIAIP_ENC_PIC_INFO *pEncPicInfo) |
| { |
| struct queue_data *queue; |
| struct vpu_frame_info *frame; |
| pBUFFER_DESCRIPTOR_TYPE stream_buffer_desc; |
| s64 timestamp; |
| |
| if (!ctx || !pEncPicInfo) |
| return -EINVAL; |
| |
| vpu_dbg(LVL_DEBUG, "Frame done(%d) - uFrameID = %d\n", |
| pEncPicInfo->uPicEncodDone, pEncPicInfo->uFrameID); |
| |
| queue = &ctx->q_data[V4L2_DST]; |
| if (!pEncPicInfo->uPicEncodDone) { |
| vpu_err("Pic Encoder Not Done\n"); |
| return -EINVAL; |
| } |
| |
| stream_buffer_desc = get_rpc_stream_buffer_desc(ctx); |
| if (stream_buffer_desc->rptr < stream_buffer_desc->start || |
| stream_buffer_desc->rptr > stream_buffer_desc->end || |
| stream_buffer_desc->wptr < stream_buffer_desc->start || |
| stream_buffer_desc->wptr > stream_buffer_desc->end || |
| stream_buffer_desc->end - stream_buffer_desc->start != |
| ctx->encoder_stream.size) { |
| vpu_err("stream buffer desc is invalid, s:%x,e:%x,r:%x,w:%x\n", |
| stream_buffer_desc->start, |
| stream_buffer_desc->end, |
| stream_buffer_desc->rptr, |
| stream_buffer_desc->wptr); |
| return -EINVAL; |
| } |
| |
| show_enc_pic_info(pEncPicInfo); |
| record_start_time(ctx, V4L2_DST); |
| |
| timestamp = get_ctx_seq_timestamp(ctx, pEncPicInfo->uFrameID); |
| |
| down(&queue->drv_q_lock); |
| frame = get_idle_frame(queue); |
| if (frame) { |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| |
| memcpy(&frame->info, pEncPicInfo, sizeof(frame->info)); |
| frame->bytesleft = frame->info.uFrameSize; |
| frame->wptr = get_ptr(stream_buffer_desc->wptr); |
| frame->rptr = get_ptr(stream_buffer_desc->rptr); |
| frame->start = get_ptr(stream_buffer_desc->start); |
| frame->end = get_ptr(stream_buffer_desc->end); |
| frame->eos = false; |
| frame->is_start = true; |
| frame->timestamp = timestamp; |
| if (attr) |
| frame->index = attr->statistic.encoded_count; |
| |
| list_add_tail(&frame->list, &queue->frame_q); |
| } else { |
| vpu_err("fail to alloc memory for frame info\n"); |
| } |
| up(&queue->drv_q_lock); |
| count_encoded_frame(ctx); |
| |
| /* Sync the write pointer to the local view of it */ |
| process_stream_output(ctx); |
| |
| return 0; |
| } |
| |
| static int handle_event_frame_release(struct vpu_ctx *ctx, u_int32 *uFrameID) |
| { |
| struct queue_data *This = &ctx->q_data[V4L2_SRC]; |
| struct vb2_data_req *p_data_req = NULL; |
| |
| if (!ctx || !uFrameID) |
| return -EINVAL; |
| |
| This = &ctx->q_data[V4L2_SRC]; |
| vpu_dbg(LVL_DEBUG, "Frame release - uFrameID = %d\n", *uFrameID); |
| p_data_req = find_vb2_data_by_sequence(This, *uFrameID); |
| if (!p_data_req) { |
| vpu_err("uFrameID[%d] is invalid\n", *uFrameID); |
| return -EINVAL; |
| } |
| if (p_data_req->vb2_buf->state == VB2_BUF_STATE_ACTIVE) |
| vb2_buffer_done(p_data_req->vb2_buf, VB2_BUF_STATE_DONE); |
| |
| return 0; |
| } |
| |
| static int handle_event_stop_done(struct vpu_ctx *ctx) |
| { |
| struct queue_data *queue; |
| struct vpu_frame_info *frame; |
| |
| WARN_ON(!ctx); |
| queue = &ctx->q_data[V4L2_DST]; |
| |
| disable_fps_sts(get_vpu_ctx_attr(ctx)); |
| |
| set_bit(VPU_ENC_STATUS_STOP_DONE, &ctx->status); |
| notify_eos(ctx); |
| |
| down(&queue->drv_q_lock); |
| frame = get_idle_frame(queue); |
| if (frame) { |
| frame->eos = true; |
| list_add_tail(&frame->list, &queue->frame_q); |
| } else { |
| vpu_err("fail to alloc memory for last frame\n"); |
| } |
| up(&queue->drv_q_lock); |
| |
| process_stream_output(ctx); |
| |
| clear_start_status(ctx); |
| init_ctx_seq_info(ctx); |
| complete(&ctx->stop_cmp); |
| |
| return 0; |
| } |
| |
| static void vpu_enc_event_handler(struct vpu_ctx *ctx, |
| u_int32 uEvent, u_int32 *event_data) |
| { |
| vpu_log_event(uEvent, ctx->str_index); |
| count_event(ctx, uEvent); |
| vpu_enc_check_mem_overstep(ctx); |
| |
| switch (uEvent) { |
| case VID_API_ENC_EVENT_START_DONE: |
| handle_event_start_done(ctx); |
| break; |
| case VID_API_ENC_EVENT_MEM_REQUEST: |
| handle_event_mem_request(ctx, |
| (MEDIAIP_ENC_MEM_REQ_DATA *)event_data); |
| break; |
| case VID_API_ENC_EVENT_PARA_UPD_DONE: |
| break; |
| case VID_API_ENC_EVENT_FRAME_DONE: |
| handle_event_frame_done(ctx, |
| (MEDIAIP_ENC_PIC_INFO *)event_data); |
| break; |
| case VID_API_ENC_EVENT_FRAME_RELEASE: |
| handle_event_frame_release(ctx, (u_int32 *)event_data); |
| break; |
| case VID_API_ENC_EVENT_STOP_DONE: |
| handle_event_stop_done(ctx); |
| break; |
| case VID_API_ENC_EVENT_FRAME_INPUT_DONE: |
| set_queue_rw_flag(&ctx->q_data[V4L2_SRC], |
| VPU_ENC_FLAG_WRITEABLE); |
| response_stop_stream(ctx); |
| submit_input_and_encode(ctx); |
| break; |
| case VID_API_ENC_EVENT_TERMINATE_DONE: |
| break; |
| case VID_API_ENC_EVENT_RESET_DONE: |
| break; |
| case VID_API_ENC_EVENT_FIRMWARE_XCPT: |
| vpu_err("firmware exception:%s\n", (char *)event_data); |
| break; |
| default: |
| vpu_err("........unknown event : 0x%x\n", uEvent); |
| break; |
| } |
| } |
| |
| static void enable_mu(struct core_device *dev) |
| { |
| u32 mu_addr; |
| |
| vpu_dbg(LVL_ALL, "enable mu for core[%d]\n", dev->id); |
| |
| rpc_init_shared_memory_encoder(&dev->shared_mem, |
| cpu_phy_to_mu(dev, dev->m0_rpc_phy), |
| dev->m0_rpc_virt, dev->rpc_buf_size, |
| &dev->rpc_actual_size); |
| rpc_set_system_cfg_value_encoder(dev->shared_mem.pSharedInterface, |
| dev->vdev->reg_rpc_system, dev->id); |
| |
| if (dev->rpc_actual_size > dev->rpc_buf_size) |
| vpu_err("rpc actual size(0x%x) > (0x%x), may occur overlay\n", |
| dev->rpc_actual_size, dev->rpc_buf_size); |
| |
| mu_addr = cpu_phy_to_mu(dev, dev->m0_rpc_phy + dev->rpc_buf_size); |
| rpc_set_print_buffer(&dev->shared_mem, mu_addr, dev->print_buf_size); |
| dev->print_buf = dev->m0_rpc_virt + dev->rpc_buf_size; |
| |
| mu_addr = cpu_phy_to_mu(dev, dev->m0_rpc_phy); |
| MU_sendMesgToFW(dev->mu_base_virtaddr, RPC_BUF_OFFSET, mu_addr); |
| |
| MU_sendMesgToFW(dev->mu_base_virtaddr, BOOT_ADDRESS, |
| dev->m0_p_fw_space_phy); |
| MU_sendMesgToFW(dev->mu_base_virtaddr, INIT_DONE, 2); |
| } |
| |
| static void get_core_supported_instance_count(struct core_device *core) |
| { |
| pENC_RPC_HOST_IFACE iface; |
| |
| iface = core->shared_mem.pSharedInterface; |
| core->supported_instance_count = |
| min_t(u32, iface->uMaxEncoderStreams, VID_API_NUM_STREAMS); |
| } |
| |
| static int re_configure_codecs(struct core_device *core) |
| { |
| int i; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| struct vpu_ctx *ctx = core->ctx[i]; |
| struct queue_data *queue; |
| |
| if (!ctx || !test_bit(VPU_ENC_STATUS_INITIALIZED, &ctx->status)) |
| continue; |
| |
| mutex_lock(&ctx->instance_mutex); |
| queue = &ctx->q_data[V4L2_SRC]; |
| if (!list_empty(&queue->drv_q)) { |
| vpu_dbg(LVL_INFO, |
| "re configure codec for core[%d]\n", core->id); |
| configure_codec(ctx); |
| submit_input_and_encode(ctx); |
| } |
| mutex_unlock(&ctx->instance_mutex); |
| } |
| |
| return 0; |
| } |
| |
| static int wait_for_start_done(struct core_device *core, int resume) |
| { |
| int ret; |
| |
| if (!core) |
| return -EINVAL; |
| |
| ret = wait_for_completion_timeout(&core->start_cmp, |
| msecs_to_jiffies(1000)); |
| if (!ret) { |
| vpu_err("error: wait for core[%d] %s done timeout!\n", |
| core->id, resume ? "resume" : "start"); |
| return -EINVAL; |
| } |
| |
| if (resume) |
| re_configure_codecs(core); |
| |
| return 0; |
| } |
| |
| static void vpu_core_start_done(struct core_device *core) |
| { |
| if (!core) |
| return; |
| |
| get_core_supported_instance_count(core); |
| core->firmware_started = true; |
| complete(&core->start_cmp); |
| |
| show_firmware_version(core, LVL_ALL); |
| } |
| |
| //This code is added for MU |
| static irqreturn_t fsl_vpu_enc_mu_isr(int irq, void *This) |
| { |
| struct core_device *dev = This; |
| u32 msg; |
| |
| MU_ReceiveMsg(dev->mu_base_virtaddr, 0, &msg); |
| if (msg == 0xaa) { |
| enable_mu(dev); |
| } else if (msg == 0x55) { |
| vpu_core_start_done(dev); |
| } else if (msg == 0xA5) { |
| /*receive snapshot done msg and wakeup complete to suspend*/ |
| complete(&dev->snap_done_cmp); |
| } else |
| queue_work(dev->workqueue, &dev->msg_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* Initialization of the MU code. */ |
| static int vpu_enc_mu_init(struct core_device *core_dev) |
| { |
| int ret = 0; |
| |
| core_dev->mu_base_virtaddr = |
| core_dev->vdev->regs_base + core_dev->reg_base; |
| WARN_ON(!core_dev->mu_base_virtaddr); |
| |
| vpu_dbg(LVL_INFO, "core[%d] irq : %d\n", core_dev->id, core_dev->irq); |
| |
| ret = devm_request_irq(core_dev->generic_dev, core_dev->irq, |
| fsl_vpu_enc_mu_isr, |
| IRQF_EARLY_RESUME, |
| "vpu_mu_isr", |
| (void *)core_dev); |
| if (ret) { |
| vpu_err("request_irq failed %d, error = %d\n", |
| core_dev->irq, ret); |
| return -EINVAL; |
| } |
| |
| if (!core_dev->vpu_mu_init) { |
| MU_Init(core_dev->mu_base_virtaddr); |
| MU_EnableRxFullInt(core_dev->mu_base_virtaddr, 0); |
| core_dev->vpu_mu_init = 1; |
| } |
| |
| return ret; |
| } |
| |
| static struct vpu_ctx *get_ctx_by_index(struct core_device *core, int index) |
| { |
| struct vpu_ctx *ctx = NULL; |
| |
| if (!core) |
| return NULL; |
| |
| if (index < 0 || index >= core->supported_instance_count) |
| return NULL; |
| |
| ctx = core->ctx[index]; |
| if (!ctx) |
| return NULL; |
| |
| if (!test_bit(VPU_ENC_STATUS_INITIALIZED, &ctx->status)) { |
| vpu_err("core[%d]'s ctx[%d] is not initialized\n", |
| core->id, index); |
| return NULL; |
| } |
| |
| if (test_bit(VPU_ENC_STATUS_CLOSED, &ctx->status)) { |
| vpu_err("core[%d]'s ctx[%d] is closed\n", |
| core->id, index); |
| return NULL; |
| } |
| |
| return ctx; |
| } |
| |
| static int process_ctx_msg(struct vpu_ctx *ctx, struct msg_header *header) |
| { |
| int ret = 0; |
| struct vpu_event_msg *msg = NULL; |
| u32 *pdata = NULL; |
| |
| if (!ctx || !header) |
| return -EINVAL; |
| |
| if (ctx->ctx_released) |
| return -EINVAL; |
| |
| msg = get_idle_msg(ctx); |
| if (!msg) { |
| vpu_err("get idle msg fail\n"); |
| return -ENOMEM; |
| } |
| |
| msg->idx = header->idx; |
| msg->msgid = header->msgid; |
| msg->number = header->msgnum; |
| pdata = msg->data; |
| ret = 0; |
| if (msg->number > ARRAY_SIZE(msg->data)) { |
| ret = alloc_msg_ext_buffer(msg, header->msgnum); |
| pdata = msg->ext_data; |
| } |
| rpc_read_msg_array(&ctx->core_dev->shared_mem, pdata, msg->number); |
| if (ret) { |
| put_idle_msg(ctx, msg); |
| return ret; |
| } |
| |
| push_back_event_msg(ctx, msg); |
| |
| return ret; |
| } |
| |
| static int process_msg(struct core_device *core) |
| { |
| struct msg_header header; |
| struct vpu_ctx *ctx = NULL; |
| int ret; |
| |
| ret = rpc_get_msg_header(&core->shared_mem, &header); |
| if (ret) |
| return ret; |
| |
| if (header.idx >= ARRAY_SIZE(core->ctx)) { |
| vpu_err("msg idx(%d) is out of range\n", header.idx); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&core->vdev->dev_mutex); |
| ctx = get_ctx_by_index(core, header.idx); |
| if (ctx != NULL) { |
| process_ctx_msg(ctx, &header); |
| queue_work(ctx->instance_wq, &ctx->instance_work); |
| } else { |
| vpu_err("msg[%d] of ctx[%d] is missed\n", |
| header.msgid, header.idx); |
| rpc_read_msg_array(&core->shared_mem, NULL, header.msgnum); |
| } |
| mutex_unlock(&core->vdev->dev_mutex); |
| |
| return 0; |
| } |
| |
| extern u_int32 rpc_MediaIPFW_Video_message_check_encoder(struct shared_addr *This); |
| static void vpu_enc_msg_run_work(struct work_struct *work) |
| { |
| struct core_device *dev = container_of(work, struct core_device, msg_work); |
| /*struct vpu_ctx *ctx;*/ |
| /*struct event_msg msg;*/ |
| struct shared_addr *This = &dev->shared_mem; |
| |
| while (rpc_MediaIPFW_Video_message_check_encoder(This) == API_MSG_AVAILABLE) { |
| process_msg(dev); |
| } |
| if (rpc_MediaIPFW_Video_message_check_encoder(This) == API_MSG_BUFFER_ERROR) |
| vpu_err("MSG num is too big to handle"); |
| } |
| |
| static void vpu_enc_msg_instance_work(struct work_struct *work) |
| { |
| struct vpu_ctx *ctx = container_of(work, struct vpu_ctx, instance_work); |
| struct vpu_event_msg *msg; |
| |
| while (1) { |
| msg = pop_event_msg(ctx); |
| if (!msg) |
| break; |
| if (msg->ext_data) |
| vpu_enc_event_handler(ctx, msg->msgid, msg->ext_data); |
| else |
| vpu_enc_event_handler(ctx, msg->msgid, msg->data); |
| |
| put_idle_msg(ctx, msg); |
| } |
| } |
| |
| static int vpu_queue_setup(struct vb2_queue *vq, |
| unsigned int *buf_count, |
| unsigned int *plane_count, |
| unsigned int psize[], |
| struct device *allocators[]) |
| { |
| struct queue_data *This = (struct queue_data *)vq->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, This->desc, |
| This->ctx->core_dev->id, This->ctx->str_index); |
| |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) { |
| if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { |
| *plane_count = 2; |
| psize[0] = This->sizeimage[0];//check alignment |
| psize[1] = This->sizeimage[1];//check colocated_size |
| } else { |
| psize[0] = This->sizeimage[0] + This->sizeimage[1]; |
| *plane_count = 1; |
| } |
| if (*buf_count < MIN_BUFFER_COUNT) |
| *buf_count = MIN_BUFFER_COUNT; |
| } else { |
| *plane_count = 1; |
| psize[0] = This->sizeimage[0];//check alignment |
| } |
| |
| if (*buf_count > VPU_MAX_BUFFER) |
| *buf_count = VPU_MAX_BUFFER; |
| if (*buf_count < 1) |
| *buf_count = 1; |
| |
| return 0; |
| } |
| |
| static int vpu_buf_prepare(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct queue_data *q_data = (struct queue_data *)vq->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| q_data->ctx->core_dev->id, q_data->ctx->str_index); |
| |
| return 0; |
| } |
| |
| |
| static int vpu_start_streaming(struct vb2_queue *q, unsigned int count) |
| { |
| struct queue_data *q_data = (struct queue_data *)q->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| q_data->ctx->core_dev->id, q_data->ctx->str_index); |
| |
| return 0; |
| } |
| |
| static void vpu_stop_streaming(struct vb2_queue *q) |
| { |
| struct queue_data *q_data = (struct queue_data *)q->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| q_data->ctx->core_dev->id, q_data->ctx->str_index); |
| clear_queue(q_data); |
| } |
| |
| static void vpu_buf_queue(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct queue_data *This = (struct queue_data *)vq->drv_priv; |
| struct vb2_data_req *data_req; |
| struct vpu_ctx *ctx = This->ctx; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, This->desc, |
| This->ctx->core_dev->id, This->ctx->str_index); |
| |
| data_req = &This->vb2_reqs[vb->index]; |
| data_req->vb2_buf = vb; |
| if (V4L2_TYPE_IS_OUTPUT(vq->type)) { |
| fill_ctx_seq(ctx, data_req); |
| fill_vb_sequence(vb, data_req->sequence); |
| } |
| list_add_tail(&data_req->list, &This->drv_q); |
| } |
| |
| static bool is_enc_dma_buf(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| |
| if (vb->memory != V4L2_MEMORY_MMAP) |
| return false; |
| |
| if (vq->mem_ops != &vb2_dma_contig_memops) |
| return false; |
| |
| return true; |
| } |
| |
| static int vpu_enc_buf_init(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct queue_data *queue = vq->drv_priv; |
| struct vpu_ctx *ctx = queue->ctx; |
| int i; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, queue->desc, |
| ctx->core_dev->id, ctx->str_index); |
| if (!is_enc_dma_buf(vb)) |
| return 0; |
| |
| for (i = 0; i < vb->num_planes; i++) |
| vpu_enc_add_dma_size(get_vpu_ctx_attr(ctx), |
| vb->planes[i].length); |
| |
| return 0; |
| } |
| |
| static void vpu_enc_buf_cleanup(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct queue_data *queue = vq->drv_priv; |
| struct vpu_ctx *ctx = queue->ctx; |
| int i; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, queue->desc, |
| ctx->core_dev->id, ctx->str_index); |
| |
| if (!is_enc_dma_buf(vb)) |
| return; |
| |
| for (i = 0; i < vb->num_planes; i++) |
| vpu_enc_sub_dma_size(get_vpu_ctx_attr(ctx), |
| vb->planes[i].length); |
| } |
| |
| static void vpu_prepare(struct vb2_queue *q) |
| { |
| struct queue_data *q_data = (struct queue_data *)q->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| q_data->ctx->core_dev->id, q_data->ctx->str_index); |
| } |
| |
| static void vpu_finish(struct vb2_queue *q) |
| { |
| struct queue_data *q_data = (struct queue_data *)q->drv_priv; |
| |
| vpu_dbg(LVL_BUF, "%s(), %s, (%d, %d)\n", __func__, q_data->desc, |
| q_data->ctx->core_dev->id, q_data->ctx->str_index); |
| } |
| |
| static struct vb2_ops vpu_enc_v4l2_qops = { |
| .queue_setup = vpu_queue_setup, |
| .buf_init = vpu_enc_buf_init, |
| .buf_cleanup = vpu_enc_buf_cleanup, |
| .wait_prepare = vpu_prepare, |
| .wait_finish = vpu_finish, |
| .buf_prepare = vpu_buf_prepare, |
| .start_streaming = vpu_start_streaming, |
| .stop_streaming = vpu_stop_streaming, |
| .buf_queue = vpu_buf_queue, |
| }; |
| |
| static void init_vb2_queue(struct queue_data *This, unsigned int type, |
| struct vpu_ctx *ctx, |
| const struct vb2_mem_ops *mem_ops, |
| gfp_t gfp_flags) |
| { |
| struct vb2_queue *vb2_q = &This->vb2_q; |
| int ret; |
| |
| vpu_log_func(); |
| |
| // initialze driver queue |
| INIT_LIST_HEAD(&This->drv_q); |
| INIT_LIST_HEAD(&This->frame_q); |
| INIT_LIST_HEAD(&This->frame_idle); |
| atomic64_set(&This->frame_count, 0); |
| // initialize vb2 queue |
| vb2_q->type = type; |
| vb2_q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; |
| vb2_q->gfp_flags = gfp_flags; |
| vb2_q->ops = &vpu_enc_v4l2_qops; |
| vb2_q->drv_priv = This; |
| if (mem_ops) |
| vb2_q->mem_ops = mem_ops; |
| else |
| vb2_q->mem_ops = &vb2_dma_contig_memops; |
| vb2_q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; |
| vb2_q->dev = &ctx->dev->plat_dev->dev; |
| ret = vb2_queue_init(vb2_q); |
| if (ret) |
| vpu_err("%s vb2_queue_init() failed (%d)!\n", |
| __func__, |
| ret |
| ); |
| else |
| This->vb2_q_inited = true; |
| } |
| |
| static void vpu_enc_init_output_queue(struct vpu_ctx *ctx, |
| struct queue_data *q) |
| { |
| WARN_ON(!ctx); |
| WARN_ON(!q); |
| |
| vpu_log_func(); |
| |
| init_vb2_queue(q, |
| V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, |
| ctx, |
| &vb2_dma_contig_memops, |
| GFP_DMA32); |
| q->type = V4L2_SRC; |
| sema_init(&q->drv_q_lock, 1); |
| q->ctx = ctx; |
| |
| q->supported_fmts = formats_yuv_enc; |
| q->fmt_count = ARRAY_SIZE(formats_yuv_enc); |
| q->current_fmt = &formats_yuv_enc[0]; |
| |
| q->width = VPU_ENC_WIDTH_DEFAULT; |
| q->height = VPU_ENC_HEIGHT_DEFAULT; |
| q->rect.left = 0; |
| q->rect.top = 0; |
| q->rect.width = VPU_ENC_WIDTH_DEFAULT; |
| q->rect.height = VPU_ENC_HEIGHT_DEFAULT; |
| scnprintf(q->desc, sizeof(q->desc), "OUTPUT"); |
| } |
| |
| static void vpu_enc_init_capture_queue(struct vpu_ctx *ctx, |
| struct queue_data *q) |
| { |
| WARN_ON(!ctx); |
| WARN_ON(!q); |
| |
| vpu_log_func(); |
| |
| init_vb2_queue(q, |
| V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, |
| ctx, |
| &vb2_vmalloc_memops, 0); |
| q->type = V4L2_DST; |
| sema_init(&q->drv_q_lock, 1); |
| q->ctx = ctx; |
| |
| q->supported_fmts = formats_compressed_enc; |
| q->fmt_count = ARRAY_SIZE(formats_compressed_enc); |
| q->current_fmt = &formats_compressed_enc[0]; |
| |
| q->width = VPU_ENC_WIDTH_DEFAULT; |
| q->height = VPU_ENC_HEIGHT_DEFAULT; |
| scnprintf(q->desc, sizeof(q->desc), "CAPTURE"); |
| } |
| |
| static void vpu_enc_init_queue_data(struct vpu_ctx *ctx) |
| { |
| vpu_enc_init_output_queue(ctx, &ctx->q_data[V4L2_SRC]); |
| vpu_enc_init_capture_queue(ctx, &ctx->q_data[V4L2_DST]); |
| } |
| |
| static void vpu_enc_release_queue_data(struct vpu_ctx *ctx) |
| { |
| vpu_enc_queue_release(&ctx->q_data[V4L2_SRC]); |
| vpu_enc_queue_release(&ctx->q_data[V4L2_DST]); |
| } |
| |
| static void vpu_ctx_power_on(struct vpu_ctx *ctx) |
| { |
| if (!ctx || !ctx->core_dev) |
| return; |
| |
| if (ctx->power_status) |
| return; |
| pm_runtime_get_sync(ctx->core_dev->generic_dev); |
| ctx->power_status = true; |
| } |
| |
| static void vpu_ctx_power_off(struct vpu_ctx *ctx) |
| { |
| if (!ctx || !ctx->core_dev) |
| return; |
| |
| if (!ctx->power_status) |
| return; |
| pm_runtime_put_sync(ctx->core_dev->generic_dev); |
| ctx->power_status = false; |
| } |
| |
| static int set_vpu_fw_addr(struct vpu_dev *dev, struct core_device *core_dev) |
| { |
| off_t reg_fw_base; |
| |
| if (!dev || !core_dev) |
| return -EINVAL; |
| |
| MU_Init(core_dev->mu_base_virtaddr); |
| MU_EnableRxFullInt(core_dev->mu_base_virtaddr, 0); |
| |
| reg_fw_base = core_dev->reg_csr_base; |
| write_vpu_reg(dev, core_dev->m0_p_fw_space_phy, reg_fw_base); |
| write_vpu_reg(dev, 0x0, reg_fw_base + 4); |
| |
| return 0; |
| } |
| |
| static void cleanup_firmware_memory(struct core_device *core_dev) |
| { |
| memset_io(core_dev->m0_p_fw_space_vir, 0, core_dev->fw_buf_size); |
| } |
| |
| static int vpu_firmware_download(struct vpu_dev *This, u_int32 core_id) |
| { |
| const struct firmware *m0_pfw = NULL; |
| const u8 *image; |
| unsigned int FW_Size = 0; |
| int ret = 0; |
| struct core_device *core_dev = &This->core_dev[core_id]; |
| char *p = This->core_dev[core_id].m0_p_fw_space_vir; |
| |
| vpu_log_func(); |
| |
| ret = request_firmware(&m0_pfw, M0FW_FILENAME, This->generic_dev); |
| if (ret) { |
| vpu_err("%s() request fw %s failed(%d)\n", |
| __func__, M0FW_FILENAME, ret); |
| |
| return ret; |
| } |
| vpu_dbg(LVL_DEBUG, "%s() request fw %s got size(%ld)\n", |
| __func__, M0FW_FILENAME, m0_pfw->size); |
| |
| image = m0_pfw->data; |
| FW_Size = min_t(u32, m0_pfw->size, This->core_dev[core_id].fw_buf_size); |
| This->core_dev[core_id].fw_actual_size = FW_Size; |
| |
| cleanup_firmware_memory(core_dev); |
| memcpy(core_dev->m0_p_fw_space_vir, image, FW_Size); |
| p[16] = This->plat_type; |
| p[17] = core_id + 1; |
| p[18] = 1; |
| set_vpu_fw_addr(This, &This->core_dev[core_id]); |
| |
| release_firmware(m0_pfw); |
| m0_pfw = NULL; |
| |
| return ret; |
| } |
| |
| static int download_vpu_firmware(struct vpu_dev *dev, |
| struct core_device *core_dev) |
| { |
| int ret = 0; |
| |
| if (!dev || !core_dev) |
| return -EINVAL; |
| |
| if (core_dev->fw_is_ready) |
| return 0; |
| |
| vpu_dbg(LVL_INFO, "download firmware for core[%d]\n", core_dev->id); |
| init_completion(&core_dev->start_cmp); |
| ret = vpu_firmware_download(dev, core_dev->id); |
| if (ret) { |
| vpu_err("error: vpu_firmware_download fail\n"); |
| goto exit; |
| } |
| wait_for_start_done(core_dev, 0); |
| if (!core_dev->firmware_started) { |
| vpu_err("core[%d] start firmware failed\n", core_dev->id); |
| ret = -EINVAL; |
| goto exit; |
| } |
| |
| set_core_fw_status(core_dev, true); |
| clear_core_hang(core_dev); |
| exit: |
| return ret; |
| } |
| |
| static bool is_valid_ctx(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return false; |
| if (!ctx->dev || !ctx->core_dev) |
| return false; |
| if (ctx->str_index >= ARRAY_SIZE(ctx->core_dev->ctx)) |
| return false; |
| |
| return true; |
| } |
| |
| static void free_instance(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| if (is_valid_ctx(ctx)) |
| ctx->core_dev->ctx[ctx->str_index] = NULL; |
| VPU_SAFE_RELEASE(ctx, kfree); |
| } |
| |
| static u32 count_free_core_slot(struct core_device *core) |
| { |
| u32 count = 0; |
| int i; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (!core->ctx[i]) |
| count++; |
| } |
| |
| return count; |
| } |
| |
| static struct core_device *find_proper_core(struct vpu_dev *dev) |
| { |
| struct core_device *core = NULL; |
| u32 maximum = 0; |
| u32 count; |
| int i; |
| int ret; |
| |
| for (i = 0; i < dev->core_num; i++) { |
| struct core_device *core_dev = &dev->core_dev[i]; |
| |
| ret = process_core_hang(core_dev); |
| if (ret) |
| continue; |
| |
| ret = download_vpu_firmware(dev, core_dev); |
| if (ret) |
| continue; |
| |
| if (core_dev->supported_instance_count == 0) |
| continue; |
| |
| count = count_free_core_slot(core_dev); |
| if (count == core_dev->supported_instance_count) |
| return core_dev; |
| |
| if (maximum < count) { |
| core = core_dev; |
| maximum = count; |
| } |
| } |
| |
| return core; |
| } |
| |
| static int request_instance(struct core_device *core, struct vpu_ctx *ctx) |
| { |
| int found = 0; |
| int idx; |
| |
| if (!core || !ctx) |
| return -EINVAL; |
| |
| for (idx = 0; idx < core->supported_instance_count; idx++) { |
| if (!core->ctx[idx]) { |
| found = 1; |
| ctx->core_dev = core; |
| ctx->str_index = idx; |
| ctx->dev = core->vdev; |
| break; |
| } |
| } |
| |
| if (!found) { |
| vpu_err("cann't request any instance\n"); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static int construct_vpu_ctx(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return -EINVAL; |
| |
| ctx->ctrl_inited = false; |
| mutex_init(&ctx->instance_mutex); |
| ctx->ctx_released = false; |
| |
| return 0; |
| } |
| |
| static struct vpu_ctx *create_and_request_instance(struct vpu_dev *dev) |
| { |
| struct core_device *core = NULL; |
| struct vpu_ctx *ctx = NULL; |
| int ret; |
| |
| if (!dev) |
| return NULL; |
| |
| core = find_proper_core(dev); |
| if (!core) |
| return NULL; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return NULL; |
| |
| ret = request_instance(core, ctx); |
| if (ret < 0) { |
| VPU_SAFE_RELEASE(ctx, kfree); |
| return NULL; |
| } |
| |
| construct_vpu_ctx(ctx); |
| vpu_ctx_power_on(ctx); |
| vpu_dbg(LVL_INFO, "request encoder instance : %d.%d\n", |
| ctx->core_dev->id, ctx->str_index); |
| |
| return ctx; |
| } |
| |
| static int init_vpu_ctx_fh(struct vpu_ctx *ctx, struct vpu_dev *dev) |
| { |
| if (!ctx || !dev) |
| return -EINVAL; |
| |
| mutex_lock(&ctx->instance_mutex); |
| |
| v4l2_fh_init(&ctx->fh, dev->pvpu_encoder_dev); |
| v4l2_fh_add(&ctx->fh); |
| ctx->fh.ctrl_handler = &ctx->ctrl_handler; |
| clear_bit(VPU_ENC_STATUS_CLOSED, &ctx->status); |
| |
| mutex_unlock(&ctx->instance_mutex); |
| |
| return 0; |
| } |
| |
| static void uninit_vpu_ctx_fh(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| mutex_lock(&ctx->instance_mutex); |
| |
| set_bit(VPU_ENC_STATUS_CLOSED, &ctx->status); |
| v4l2_fh_del(&ctx->fh); |
| v4l2_fh_exit(&ctx->fh); |
| |
| mutex_unlock(&ctx->instance_mutex); |
| } |
| |
| static void cancel_vpu_ctx(struct vpu_ctx *ctx) |
| { |
| cancel_work_sync(&ctx->instance_work); |
| cleanup_ctx_msg_queue(ctx); |
| } |
| |
| static void uninit_vpu_ctx(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| clear_bit(VPU_ENC_STATUS_INITIALIZED, &ctx->status); |
| cancel_vpu_ctx(ctx); |
| if (ctx->instance_wq) { |
| destroy_workqueue(ctx->instance_wq); |
| ctx->instance_wq = NULL; |
| } |
| mutex_lock(&ctx->instance_mutex); |
| vpu_enc_free_stream(ctx); |
| |
| ctx->ctx_released = true; |
| mutex_unlock(&ctx->instance_mutex); |
| } |
| |
| static int init_vpu_ctx(struct vpu_ctx *ctx) |
| { |
| INIT_WORK(&ctx->instance_work, vpu_enc_msg_instance_work); |
| ctx->instance_wq = alloc_workqueue("vpu_instance", |
| WQ_UNBOUND | WQ_MEM_RECLAIM, 1); |
| if (!ctx->instance_wq) { |
| vpu_err("error: unable to alloc workqueue for ctx\n"); |
| return -ENOMEM; |
| } |
| |
| init_ctx_msg_queue(ctx); |
| |
| vpu_enc_init_queue_data(ctx); |
| init_completion(&ctx->stop_cmp); |
| |
| set_bit(VPU_ENC_STATUS_INITIALIZED, &ctx->status); |
| ctx->core_dev->ctx[ctx->str_index] = ctx; |
| |
| return 0; |
| } |
| |
| static int show_encoder_param(struct vpu_attr *attr, |
| pMEDIAIP_ENC_PARAM param, char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, |
| "encoder param:[setting/take effect]\n"); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Codec Mode", |
| attr->param.eCodecMode, param->eCodecMode); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Profile", |
| attr->param.eProfile, param->eProfile); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Level", |
| attr->param.uLevel, param->uLevel); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Frame Rate", |
| attr->param.uFrameRate, param->uFrameRate); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Stride", |
| attr->param.uSrcStride, param->uSrcStride); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Width", |
| attr->param.uSrcWidth, param->uSrcWidth); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Height", |
| attr->param.uSrcHeight, param->uSrcHeight); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Offset x", |
| attr->param.uSrcOffset_x, param->uSrcOffset_x); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Offset y", |
| attr->param.uSrcOffset_y, param->uSrcOffset_y); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Crop Width", |
| attr->param.uSrcCropWidth, param->uSrcCropWidth); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Source Crop Height", |
| attr->param.uSrcCropHeight, |
| param->uSrcCropHeight); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Out Width", |
| attr->param.uOutWidth, param->uOutWidth); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Out Height", |
| attr->param.uOutHeight, param->uOutHeight); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "I Frame Interval", |
| attr->param.uIFrameInterval, |
| param->uIFrameInterval); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Bframes", |
| attr->param.uGopBLength, param->uGopBLength); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Low Latency Mode", |
| attr->param.uLowLatencyMode, |
| param->uLowLatencyMode); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Bitrate Mode", |
| attr->param.eBitRateMode, param->eBitRateMode); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Target Bitrate", |
| attr->param.uTargetBitrate, |
| param->uTargetBitrate); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Min Bitrate", |
| attr->param.uMinBitRate, param->uMinBitRate); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "Max Bitrate", |
| attr->param.uMaxBitRate, param->uMaxBitRate); |
| num += scnprintf(buf + num, size - num, |
| "\t%-18s:%10d;%10d\n", "QP", |
| attr->param.uInitSliceQP, |
| param->uInitSliceQP); |
| |
| return num; |
| } |
| |
| static int show_queue_buffer_info(struct queue_data *queue, char *buf, u32 size) |
| { |
| int i; |
| int num = 0; |
| |
| for (i = 0; i < queue->vb2_q.num_buffers; i++) { |
| struct vb2_buffer *vb = queue->vb2_q.bufs[i]; |
| |
| num += scnprintf(buf + num, size - num, " %d", vb->state); |
| } |
| |
| return num; |
| } |
| |
| static int show_cmd_event(struct vpu_statistic *statistic, int i, |
| char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "\t(%2d) ", i); |
| |
| if (i <= GTB_ENC_CMD_RESERVED) |
| num += scnprintf(buf + num, PAGE_SIZE - num, "%-28s:%12ld;", |
| get_cmd_str(i), statistic->cmd[i]); |
| else |
| num += scnprintf(buf + num, PAGE_SIZE - num, "%-28s:%12s;", |
| "", ""); |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, " "); |
| if (i <= VID_API_ENC_EVENT_RESERVED) |
| num += scnprintf(buf + num, PAGE_SIZE - num, "%-34s:%12ld;", |
| get_event_str(i), statistic->event[i]); |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "\n"); |
| |
| return num; |
| } |
| |
| static int show_cmd_event_infos(struct vpu_statistic *statistic, |
| char *buf, u32 size) |
| { |
| int num = 0; |
| int i; |
| int count; |
| |
| num += scnprintf(buf + num, size - num, "command/event:\n"); |
| |
| count = max(GTB_ENC_CMD_RESERVED, VID_API_ENC_EVENT_RESERVED); |
| for (i = 0; i <= count; i++) |
| num += show_cmd_event(statistic, i, buf + num, size - num); |
| |
| num += scnprintf(buf + num, size - num, "current status:\n"); |
| num += scnprintf(buf + num, size - num, |
| "\t%-10s:%36s;%10ld.%06ld\n", "commond", |
| get_cmd_str(statistic->current_cmd), |
| statistic->ts_cmd.tv_sec, |
| statistic->ts_cmd.tv_nsec / 1000); |
| num += scnprintf(buf + num, size - num, |
| "\t%-10s:%36s;%10ld.%06ld\n", "event", |
| get_event_str(statistic->current_event), |
| statistic->ts_event.tv_sec, |
| statistic->ts_event.tv_nsec / 1000); |
| |
| return num; |
| } |
| |
| static int show_single_fps_info(struct vpu_fps_sts *fps, char *buf, u32 size) |
| { |
| const u32 COEF = VPU_FPS_COEF; |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, "%3ld.", fps->fps / COEF); |
| num += scnprintf(buf + num, size - num, "%02ld", fps->fps % COEF); |
| if (fps->thd) |
| num += scnprintf(buf + num, size - num, "(%ds)", fps->thd); |
| else |
| num += scnprintf(buf + num, size - num, "(avg)"); |
| |
| return num; |
| } |
| |
| static int show_fps_info(struct vpu_fps_sts *fps, int count, |
| char *buf, u32 size) |
| { |
| int i; |
| int num = 0; |
| |
| for (i = 0; i < count; i++) { |
| if (i > 0) |
| num += scnprintf(buf + num, size - num, " "); |
| num += show_single_fps_info(&fps[i], buf + num, size - num); |
| } |
| |
| return num; |
| } |
| |
| static int show_frame_sts(struct vpu_statistic *statistic, char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, |
| "frame count:\n"); |
| num += scnprintf(buf + num, size - num, "\t%-24s:%ld\n", |
| "dbuf input yuv count", statistic->yuv_count); |
| num += scnprintf(buf + num, size - num, "\t%-24s:%ld\n", |
| "encode frame count", statistic->encoded_count); |
| num += scnprintf(buf + num, size - num, "\t%-24s:%ld\n", |
| "dqbuf output h264 count", statistic->h264_count); |
| |
| num += scnprintf(buf + num, size - num, "\t%-24s:", "actual fps:"); |
| num += show_fps_info(statistic->fps, ARRAY_SIZE(statistic->fps), |
| buf + num, PAGE_SIZE - num); |
| num += scnprintf(buf + num, size - num, "\n"); |
| num += scnprintf(buf + num, size - num, "\t%-24s:%ld\n", |
| "timestamp overwrite", statistic->timestamp_overwrite); |
| |
| return num; |
| } |
| |
| static int show_strip_info(struct vpu_statistic *statistic, char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, |
| "strip data frame count:\n"); |
| num += scnprintf(buf + num, size - num, |
| "\t fw :%16ld (max : %ld; total : %ld)\n", |
| statistic->strip_sts.fw.count, |
| statistic->strip_sts.fw.max, |
| statistic->strip_sts.fw.total); |
| num += scnprintf(buf + num, size - num, |
| "\t begin :%16ld (max : %ld; total : %ld)\n", |
| statistic->strip_sts.begin.count, |
| statistic->strip_sts.begin.max, |
| statistic->strip_sts.begin.total); |
| num += scnprintf(buf + num, size - num, |
| "\t eos :%16ld (max : %ld; total : %ld)\n", |
| statistic->strip_sts.eos.count, |
| statistic->strip_sts.eos.max, |
| statistic->strip_sts.eos.total); |
| |
| return num; |
| } |
| |
| static int show_v4l2_buf_status(struct vpu_ctx *ctx, char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, "V4L2 Buffer Status: "); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "("); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:dequeued,", VB2_BUF_STATE_DEQUEUED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:preparing,", VB2_BUF_STATE_PREPARING); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:prepared,", VB2_BUF_STATE_PREPARED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:queued,", VB2_BUF_STATE_QUEUED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:requeueing,", VB2_BUF_STATE_REQUEUEING); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:active,", VB2_BUF_STATE_ACTIVE); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:done,", VB2_BUF_STATE_DONE); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:error", VB2_BUF_STATE_ERROR); |
| num += scnprintf(buf + num, PAGE_SIZE - num, ")\n"); |
| num += scnprintf(buf + num, size - num, "\tOUTPUT:"); |
| num += show_queue_buffer_info(&ctx->q_data[V4L2_SRC], |
| buf + num, |
| size - num); |
| num += scnprintf(buf + num, size - num, " CAPTURE:"); |
| num += show_queue_buffer_info(&ctx->q_data[V4L2_DST], |
| buf + num, |
| size - num); |
| num += scnprintf(buf + num, size - num, "\n"); |
| |
| return num; |
| } |
| |
| static int show_instance_status(struct vpu_ctx *ctx, char *buf, u32 size) |
| { |
| int num = 0; |
| |
| num += scnprintf(buf + num, size - num, "instance status:\n"); |
| num += scnprintf(buf + num, size - num, |
| "\t%-13s:0x%lx\n", "status", ctx->status); |
| num += scnprintf(buf + num, size - num, |
| "\t%-13s:%d\n", "frozen count", ctx->frozen_count); |
| |
| return num; |
| } |
| |
| static int show_instance_others(struct vpu_attr *attr, char *buf, u32 size) |
| { |
| int num = 0; |
| struct vpu_ctx *ctx = NULL; |
| struct vpu_dev *vpudev = attr->core->vdev; |
| |
| num += scnprintf(buf + num, size - num, "others:\n"); |
| if (attr->ts_start[V4L2_SRC] && attr->ts_start[V4L2_DST]) { |
| unsigned long latency; |
| |
| latency = attr->ts_start[V4L2_DST] - attr->ts_start[V4L2_SRC]; |
| num += scnprintf(buf + num, size - num, |
| "\tlatency(ms) :%ld\n", latency); |
| } |
| |
| num += scnprintf(buf + num, size - num, |
| "\ttotal dma size :%ld\n", |
| atomic64_read(&attr->total_dma_size)); |
| num += scnprintf(buf + num, size - num, |
| "\ttotal event msg obj count :%ld\n", |
| attr->msg_count); |
| num += scnprintf(buf + num, size - num, |
| "\ttotal msg ext data count :%lld\n", |
| get_total_ext_data_number()); |
| |
| mutex_lock(&vpudev->dev_mutex); |
| ctx = get_vpu_attr_ctx(attr); |
| if (ctx) { |
| num += scnprintf(buf + num, size - num, |
| "\ttotal frame obj count :%ld\n", |
| atomic64_read(&ctx->q_data[V4L2_DST].frame_count)); |
| |
| if (test_bit(VPU_ENC_STATUS_HANG, &ctx->status)) |
| num += scnprintf(buf + num, size - num, "<hang>\n"); |
| } else { |
| num += scnprintf(buf + num, size - num, |
| "<instance has been released>\n"); |
| } |
| mutex_unlock(&vpudev->dev_mutex); |
| |
| return num; |
| } |
| |
| static ssize_t show_instance_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct vpu_attr *vpu_attr; |
| struct vpu_dev *vpudev; |
| struct vpu_statistic *statistic; |
| struct vpu_ctx *ctx; |
| pMEDIAIP_ENC_PARAM param; |
| int num = 0; |
| |
| vpu_attr = container_of(attr, struct vpu_attr, dev_attr); |
| vpudev = vpu_attr->core->vdev; |
| |
| num += scnprintf(buf + num, PAGE_SIZE, |
| "pid: %d; tgid: %d\n", vpu_attr->pid, vpu_attr->tgid); |
| |
| param = rpc_get_enc_param(&vpu_attr->core->shared_mem, vpu_attr->index); |
| num += show_encoder_param(vpu_attr, param, buf + num, PAGE_SIZE - num); |
| |
| statistic = &vpu_attr->statistic; |
| num += show_cmd_event_infos(statistic, buf + num, PAGE_SIZE - num); |
| num += show_frame_sts(statistic, buf + num, PAGE_SIZE - num); |
| num += show_strip_info(statistic, buf + num, PAGE_SIZE - num); |
| |
| mutex_lock(&vpudev->dev_mutex); |
| ctx = get_vpu_attr_ctx(vpu_attr); |
| if (ctx) { |
| num += show_v4l2_buf_status(ctx, buf + num, PAGE_SIZE - num); |
| num += show_instance_status(ctx, buf + num, PAGE_SIZE - num); |
| } |
| mutex_unlock(&vpudev->dev_mutex); |
| |
| num += show_instance_others(vpu_attr, buf + num, PAGE_SIZE - num); |
| |
| return num; |
| } |
| |
| static ssize_t show_core_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct core_device *core = NULL; |
| char *fw = NULL; |
| int num = 0; |
| |
| core = container_of(attr, struct core_device, core_attr); |
| fw = core->m0_p_fw_space_vir; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "core[%d] info:\n", core->id); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "vpu mu id : %d\n", core->vpu_mu_id); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "irq : %d\n", core->irq); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reg mu mcu : 0x%08x 0x%08x\n", |
| core->reg_base, core->reg_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reg csr : 0x%08x 0x%08x\n", |
| core->reg_csr_base, core->reg_csr_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "fw space phy : 0x%08x\n", core->m0_p_fw_space_phy); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "fw space size : 0x%08x\n", core->fw_buf_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "fw actual size : 0x%08x\n", core->fw_actual_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "rpc phy : 0x%08x\n", core->m0_rpc_phy); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "rpc buf size : 0x%08x\n", core->rpc_buf_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "rpc actual size : 0x%08x\n", core->rpc_actual_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "print buf phy : 0x%08x\n", |
| core->m0_rpc_phy + core->rpc_buf_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "print buf size : 0x%08x\n", core->print_buf_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "max instance num: %d\n", |
| core->supported_instance_count); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "fw_is_ready : %d\n", core->fw_is_ready); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "firmware_started: %d\n", core->firmware_started); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "hang : %d\n", core->hang); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reset times : %ld\n", core->reset_times); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "heartbeat : %02x\n", core->vdev->heartbeat); |
| if (core->fw_is_ready) { |
| pENC_RPC_HOST_IFACE iface = core->shared_mem.pSharedInterface; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "firmware version: %d.%d.%d\n", |
| (iface->FWVersion & 0x00ff0000) >> 16, |
| (iface->FWVersion & 0x0000ff00) >> 8, |
| iface->FWVersion & 0x000000ff); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "fw info : 0x%02x 0x%02x\n", fw[16], fw[17]); |
| } |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "driver version : %s\n", VPU_ENC_DRIVER_VERSION); |
| |
| return num; |
| } |
| |
| static int show_vb2_memory(struct vb2_buffer *vb, char *buf, u32 size) |
| { |
| int num = 0; |
| int i; |
| |
| for (i = 0; i < vb->num_planes; i++) { |
| num += scnprintf(buf + num, size - num, "0x%8x 0x%x", |
| get_vb2_plane_phy_addr(vb, i), |
| vb->planes[i].length); |
| if (i == vb->num_planes - 1) |
| num += scnprintf(buf + num, size - num, "\n"); |
| else |
| num += scnprintf(buf + num, size - num, "; "); |
| } |
| |
| return num; |
| } |
| |
| static int show_queue_memory(struct queue_data *queue, char *buf, u32 size, |
| char *prefix) |
| { |
| int num = 0; |
| int i; |
| |
| num += scnprintf(buf + num, size - num, "%s%4s v4l2buf :\n", prefix, |
| queue->type == V4L2_SRC ? "YUV" : "H264"); |
| |
| for (i = 0; i < queue->vb2_q.num_buffers; i++) { |
| struct vb2_buffer *vb = queue->vb2_q.bufs[i]; |
| |
| num += scnprintf(buf + num, size - num, "%s%18s", prefix, ""); |
| num += show_vb2_memory(vb, buf + num, size - num); |
| } |
| |
| return num; |
| } |
| |
| static int show_ctx_memory_details(struct vpu_ctx *ctx, char *buf, u32 size, |
| char *prefix) |
| { |
| int num = 0; |
| int i; |
| |
| if (!ctx) |
| return 0; |
| |
| num += scnprintf(buf + num, size - num, "%smemory details:\n", prefix); |
| num += scnprintf(buf + num, size - num, "%sencFrames :\n", prefix); |
| for (i = 0; i < MEDIAIP_MAX_NUM_WINDSOR_SRC_FRAMES; i++) { |
| num += scnprintf(buf + num, size - num, "%s%14s", prefix, ""); |
| num += scnprintf(buf + num, size - num, "[%d] 0x%8llx 0x%x\n", |
| i, |
| ctx->encFrame[i].phy_addr, |
| ctx->encFrame[i].size); |
| } |
| |
| num += scnprintf(buf + num, size - num, "%srefFrames :\n", prefix); |
| for (i = 0; i < MEDIAIP_MAX_NUM_WINDSOR_REF_FRAMES; i++) { |
| num += scnprintf(buf + num, size - num, "%s%14s", prefix, ""); |
| num += scnprintf(buf + num, size - num, "[%d] 0x%8llx 0x%x\n", |
| i, |
| ctx->refFrame[i].phy_addr, |
| ctx->refFrame[i].size); |
| } |
| |
| num += scnprintf(buf + num, size - num, "%sactFrames :\n", prefix); |
| num += scnprintf(buf + num, size - num, "%s%18s", prefix, ""); |
| num += scnprintf(buf + num, size - num, "0x%8llx 0x%x\n", |
| ctx->actFrame.phy_addr, ctx->actFrame.size); |
| |
| num += scnprintf(buf + num, size - num, "%sencoderStream:\n", prefix); |
| num += scnprintf(buf + num, size - num, "%s%18s", prefix, ""); |
| num += scnprintf(buf + num, size - num, "0x%8llx 0x%x\n", |
| ctx->encoder_stream.phy_addr, ctx->encoder_stream.size); |
| |
| for (i = 0; i < ARRAY_SIZE(ctx->q_data); i++) { |
| struct queue_data *queue = &ctx->q_data[i]; |
| |
| if (queue->vb2_q.memory != V4L2_MEMORY_MMAP) |
| continue; |
| if (queue->vb2_q.mem_ops != &vb2_dma_contig_memops) |
| continue; |
| |
| num += show_queue_memory(queue, buf + num, size - num, prefix); |
| } |
| |
| return num; |
| } |
| |
| static ssize_t show_memory_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct vpu_dev *vdev = dev_get_drvdata(dev); |
| unsigned long total_dma_size = 0; |
| int num = 0; |
| int i; |
| int j; |
| int core_id = (show_detail_index >> 8) & 0xff; |
| int ctx_id = show_detail_index & 0xff; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "dma memory usage:\n"); |
| for (i = 0; i < vdev->core_num; i++) { |
| struct core_device *core = &vdev->core_dev[i]; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "core[%d]\n", i); |
| |
| for (j = 0; j < ARRAY_SIZE(core->attr); j++) { |
| struct vpu_attr *attr = &core->attr[j]; |
| unsigned long size; |
| |
| size = atomic64_read(&attr->total_dma_size); |
| total_dma_size += size; |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "\t[%d] : %ld\n", j, size); |
| if (core_id != i || ctx_id != j) |
| continue; |
| mutex_lock(&vdev->dev_mutex); |
| num += show_ctx_memory_details(core->ctx[j], |
| buf + num, |
| PAGE_SIZE - num, |
| "\t\t"); |
| mutex_unlock(&vdev->dev_mutex); |
| } |
| } |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "total dma : %ld\n", total_dma_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "total reserved memory : %ld\n", |
| vdev->reserved_mem.bytesused); |
| show_detail_index = VPU_DETAIL_INDEX_DFT; |
| |
| return num; |
| } |
| |
| static ssize_t store_memory_info_index(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| long val = VPU_DETAIL_INDEX_DFT; |
| |
| if (!kstrtol(buf, 0, &val)) |
| show_detail_index = val; |
| |
| return count; |
| } |
| DEVICE_ATTR(meminfo, 0664, show_memory_info, store_memory_info_index); |
| |
| static ssize_t show_buffer_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct vpu_dev *vdev = dev_get_drvdata(dev); |
| int num = 0; |
| int i; |
| int j; |
| |
| mutex_lock(&vdev->dev_mutex); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "vpu encoder buffers:\t"); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "("); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:dequeued,", VB2_BUF_STATE_DEQUEUED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:preparing,", VB2_BUF_STATE_PREPARING); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:prepared,", VB2_BUF_STATE_PREPARED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:queued,", VB2_BUF_STATE_QUEUED); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:requeueing,", VB2_BUF_STATE_REQUEUEING); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:active,", VB2_BUF_STATE_ACTIVE); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:done,", VB2_BUF_STATE_DONE); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %d:error", VB2_BUF_STATE_ERROR); |
| num += scnprintf(buf + num, PAGE_SIZE - num, ")\n"); |
| |
| for (i = 0; i < vdev->core_num; i++) { |
| struct core_device *core = &vdev->core_dev[i]; |
| |
| if (!core->supported_instance_count) |
| continue; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "core[%d]\n", i); |
| for (j = 0; j < core->supported_instance_count; j++) { |
| struct vpu_ctx *ctx = core->ctx[j]; |
| |
| if (!ctx) |
| continue; |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "\t[%d]: ", j); |
| num += scnprintf(buf + num, |
| PAGE_SIZE - num, "OUTPUT:"); |
| num += show_queue_buffer_info(&ctx->q_data[V4L2_SRC], |
| buf + num, |
| PAGE_SIZE - num); |
| num += scnprintf(buf + num, |
| PAGE_SIZE - num, " CAPTURE:"); |
| num += show_queue_buffer_info(&ctx->q_data[V4L2_DST], |
| buf + num, |
| PAGE_SIZE - num); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "\n"); |
| } |
| } |
| mutex_unlock(&vdev->dev_mutex); |
| |
| return num; |
| } |
| DEVICE_ATTR(buffer, 0444, show_buffer_info, NULL); |
| |
| static ssize_t show_fpsinfo(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct vpu_dev *vdev = dev_get_drvdata(dev); |
| int num = 0; |
| int i; |
| int j; |
| |
| for (i = 0; i < vdev->core_num; i++) { |
| struct core_device *core = &vdev->core_dev[i]; |
| |
| if (!core->supported_instance_count) |
| continue; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, "core[%d]\n", i); |
| for (j = 0; j < core->supported_instance_count; j++) { |
| struct vpu_attr *attr = &core->attr[j]; |
| |
| if (!attr->created) |
| continue; |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "\t[%d]", j); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| " %3d(setting) ", |
| attr->param.uFrameRate); |
| num += show_fps_info(attr->statistic.fps, |
| ARRAY_SIZE(attr->statistic.fps), |
| buf + num, PAGE_SIZE - num); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "\n"); |
| } |
| } |
| |
| return num; |
| } |
| DEVICE_ATTR(fpsinfo, 0444, show_fpsinfo, NULL); |
| |
| static ssize_t show_vpuinfo(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct vpu_dev *vdev = dev_get_drvdata(dev); |
| int num = 0; |
| |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "core number : %d\n", vdev->core_num); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "platform type : %d\n", vdev->plat_type); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reg-vpu : 0x%8x 0x%08x\n", |
| vdev->reg_vpu_base, vdev->reg_vpu_size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reg-rpc-system : 0x%08x\n", |
| vdev->reg_rpc_system); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "reserved-memory : 0x%08lx 0x%08lx\n", |
| vdev->reserved_mem.phy_addr, vdev->reserved_mem.size); |
| num += scnprintf(buf + num, PAGE_SIZE - num, "supported resolution :"); |
| num += scnprintf(buf + num, PAGE_SIZE - num, " %dx%d(min);", |
| vdev->supported_size.min_width, |
| vdev->supported_size.min_height); |
| num += scnprintf(buf + num, PAGE_SIZE - num, " %dx%d(step);", |
| vdev->supported_size.step_width, |
| vdev->supported_size.step_height); |
| num += scnprintf(buf + num, PAGE_SIZE - num, " %dx%d(max)\n", |
| vdev->supported_size.max_width, |
| vdev->supported_size.max_height); |
| num += scnprintf(buf + num, PAGE_SIZE - num, |
| "supported frame rate : %d(min); %d(step); %d(max)\n", |
| vdev->supported_fps.min, |
| vdev->supported_fps.step, |
| vdev->supported_fps.max); |
| |
| return num; |
| } |
| DEVICE_ATTR(vpuinfo, 0444, show_vpuinfo, NULL); |
| |
| static void reset_statistic(struct vpu_attr *attr) |
| { |
| if (!attr) |
| return; |
| |
| memset(&attr->statistic, 0, sizeof(attr->statistic)); |
| attr->statistic.current_cmd = GTB_ENC_CMD_NOOP; |
| attr->statistic.current_event = VID_API_EVENT_UNDEFINED; |
| } |
| |
| static int init_vpu_attr_fps_sts(struct vpu_attr *attr) |
| { |
| const unsigned int THDS[] = VPU_FPS_STS_THDS; |
| int i; |
| |
| for (i = 0; i < VPU_FPS_STS_CNT; i++) { |
| if (i < ARRAY_SIZE(THDS)) |
| attr->statistic.fps[i].thd = THDS[i]; |
| else |
| attr->statistic.fps[i].thd = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int enable_fps_sts(struct vpu_attr *attr) |
| { |
| int i; |
| struct vpu_statistic *sts = &attr->statistic; |
| |
| sts->fps_sts_enable = true; |
| |
| for (i = 0; i < VPU_FPS_STS_CNT; i++) { |
| getrawmonotonic(&sts->fps[i].ts); |
| sts->fps[i].frame_number = sts->encoded_count; |
| } |
| |
| return 0; |
| } |
| |
| static int disable_fps_sts(struct vpu_attr *attr) |
| { |
| attr->statistic.fps_sts_enable = false; |
| |
| return 0; |
| } |
| |
| static int init_vpu_attr(struct vpu_attr *attr) |
| { |
| if (!attr || !attr->core) |
| return -EINVAL; |
| |
| reset_statistic(attr); |
| memset(&attr->param, 0, sizeof(attr->param)); |
| attr->pid = current->pid; |
| attr->tgid = current->tgid; |
| if (!attr->created) { |
| device_create_file(attr->core->generic_dev, &attr->dev_attr); |
| attr->created = true; |
| } |
| |
| init_vpu_attr_fps_sts(attr); |
| |
| return 0; |
| } |
| |
| static int release_instance(struct vpu_ctx *ctx) |
| { |
| struct vpu_dev *dev; |
| |
| if (!ctx || !ctx->dev) |
| return -EINVAL; |
| |
| if (!test_bit(VPU_ENC_STATUS_CLOSED, &ctx->status)) |
| return 0; |
| if (!test_bit(VPU_ENC_STATUS_FORCE_RELEASE, &ctx->status)) { |
| if (test_bit(VPU_ENC_STATUS_START_SEND, &ctx->status) && |
| !test_bit(VPU_ENC_STATUS_STOP_DONE, &ctx->status)) |
| return -EINVAL; |
| } |
| |
| dev = ctx->dev; |
| |
| uninit_vpu_ctx(ctx); |
| vpu_enc_free_ctrls(ctx); |
| vpu_enc_release_queue_data(ctx); |
| vpu_enc_free_mem(ctx, get_rpc_mem_pool(ctx)); |
| |
| vpu_ctx_power_off(ctx); |
| free_instance(ctx); |
| |
| return 0; |
| } |
| |
| static int try_to_release_idle_instance(struct vpu_dev *dev) |
| { |
| int i; |
| int j; |
| |
| if (!dev) |
| return -EINVAL; |
| |
| for (i = 0; i < dev->core_num; i++) { |
| if (dev->core_dev[i].hang) |
| set_core_force_release(&dev->core_dev[i]); |
| for (j = 0; j < dev->core_dev[i].supported_instance_count; j++) |
| release_instance(dev->core_dev[i].ctx[j]); |
| } |
| |
| return 0; |
| } |
| |
| struct vpu_attr *get_vpu_ctx_attr(struct vpu_ctx *ctx) |
| { |
| WARN_ON(!ctx || !ctx->core_dev); |
| |
| if (ctx->str_index >= ctx->core_dev->supported_instance_count) |
| return NULL; |
| |
| return &ctx->core_dev->attr[ctx->str_index]; |
| } |
| |
| struct vpu_ctx *get_vpu_attr_ctx(struct vpu_attr *attr) |
| { |
| WARN_ON(!attr || !attr->core); |
| |
| if (attr->index >= attr->core->supported_instance_count) |
| return NULL; |
| |
| return attr->core->ctx[attr->index]; |
| } |
| |
| static int vpu_enc_v4l2_open(struct file *filp) |
| { |
| struct video_device *vdev = video_devdata(filp); |
| struct vpu_dev *dev = video_get_drvdata(vdev); |
| struct vpu_ctx *ctx = NULL; |
| int ret; |
| |
| vpu_log_func(); |
| |
| mutex_lock(&dev->dev_mutex); |
| try_to_release_idle_instance(dev); |
| |
| pm_runtime_get_sync(dev->generic_dev); |
| ctx = create_and_request_instance(dev); |
| pm_runtime_put_sync(dev->generic_dev); |
| if (!ctx) { |
| mutex_unlock(&dev->dev_mutex); |
| vpu_err("failed to create encoder ctx\n"); |
| return -ENOMEM; |
| } |
| |
| init_vpu_attr(get_vpu_ctx_attr(ctx)); |
| ret = init_vpu_ctx(ctx); |
| if (ret) { |
| mutex_unlock(&dev->dev_mutex); |
| vpu_err("init vpu ctx fail\n"); |
| goto error; |
| } |
| |
| initialize_enc_param(ctx); |
| vpu_enc_setup_ctrls(ctx); |
| |
| init_vpu_ctx_fh(ctx, dev); |
| init_ctx_seq_info(ctx); |
| filp->private_data = &ctx->fh; |
| mutex_unlock(&dev->dev_mutex); |
| |
| return 0; |
| error: |
| mutex_lock(&dev->dev_mutex); |
| set_bit(VPU_ENC_STATUS_FORCE_RELEASE, &ctx->status); |
| VPU_SAFE_RELEASE(ctx, release_instance); |
| mutex_unlock(&dev->dev_mutex); |
| return ret; |
| } |
| |
| static int vpu_enc_v4l2_release(struct file *filp) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(filp->private_data); |
| struct vpu_dev *dev = ctx->dev; |
| |
| vpu_log_func(); |
| |
| request_eos(ctx); |
| wait_for_stop_done(ctx); |
| |
| mutex_lock(&dev->dev_mutex); |
| |
| uninit_vpu_ctx_fh(ctx); |
| filp->private_data = NULL; |
| |
| VPU_SAFE_RELEASE(ctx, release_instance); |
| mutex_unlock(&dev->dev_mutex); |
| |
| return 0; |
| } |
| |
| static unsigned int vpu_enc_v4l2_poll(struct file *filp, poll_table *wait) |
| { |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(filp->private_data); |
| struct vb2_queue *src_q, *dst_q; |
| unsigned int rc = 0; |
| |
| vpu_dbg(LVL_FUNC, "%s(), event: 0x%lx\n", __func__, |
| poll_requested_events(wait)); |
| |
| poll_wait(filp, &ctx->fh.wait, wait); |
| |
| if (v4l2_event_pending(&ctx->fh)) { |
| vpu_dbg(LVL_DEBUG, "%s() v4l2_event_pending\n", __func__); |
| rc |= POLLPRI; |
| } |
| |
| src_q = &ctx->q_data[V4L2_SRC].vb2_q; |
| dst_q = &ctx->q_data[V4L2_DST].vb2_q; |
| |
| if ((!src_q->streaming || list_empty(&src_q->queued_list)) |
| && (!dst_q->streaming || list_empty(&dst_q->queued_list))) { |
| rc |= POLLERR; |
| return rc; |
| } |
| |
| poll_wait(filp, &src_q->done_wq, wait); |
| if (!list_empty(&src_q->done_list)) |
| rc |= POLLOUT | POLLWRNORM; |
| poll_wait(filp, &dst_q->done_wq, wait); |
| if (!list_empty(&dst_q->done_list)) |
| rc |= POLLIN | POLLRDNORM; |
| |
| return rc; |
| } |
| |
| static int vpu_enc_v4l2_mmap(struct file *filp, struct vm_area_struct *vma) |
| { |
| long ret = -EPERM; |
| unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; |
| struct queue_data *q_data; |
| enum QUEUE_TYPE type; |
| |
| struct vpu_ctx *ctx = v4l2_fh_to_ctx(filp->private_data); |
| |
| vpu_log_func(); |
| |
| if (ctx) { |
| type = offset >> MMAP_BUF_TYPE_SHIFT; |
| q_data = &ctx->q_data[type]; |
| |
| offset &= ~MMAP_BUF_TYPE_MASK; |
| offset = offset >> PAGE_SHIFT; |
| vma->vm_pgoff = offset; |
| ret = vpu_enc_queue_mmap(q_data, vma); |
| } |
| |
| return ret; |
| } |
| |
| static const struct v4l2_file_operations vpu_enc_v4l2_fops = { |
| .owner = THIS_MODULE, |
| .open = vpu_enc_v4l2_open, |
| .unlocked_ioctl = video_ioctl2, |
| .release = vpu_enc_v4l2_release, |
| .poll = vpu_enc_v4l2_poll, |
| .mmap = vpu_enc_v4l2_mmap, |
| }; |
| |
| static struct video_device vpu_enc_v4l2_videodevice = { |
| .name = "vpu encoder", |
| .fops = &vpu_enc_v4l2_fops, |
| .ioctl_ops = &vpu_enc_v4l2_ioctl_ops, |
| .vfl_dir = VFL_DIR_M2M, |
| }; |
| |
| static void vpu_enc_setup(struct vpu_dev *This) |
| { |
| const off_t offset = SCB_XREG_SLV_BASE + SCB_SCB_BLK_CTRL; |
| uint32_t read_data = 0; |
| |
| vpu_log_func(); |
| |
| write_vpu_reg(This, 0x1, offset + SCB_BLK_CTRL_SCB_CLK_ENABLE_SET); |
| write_vpu_reg(This, 0xffffffff, 0x70190); |
| write_vpu_reg(This, 0xffffffff, offset + SCB_BLK_CTRL_XMEM_RESET_SET); |
| write_vpu_reg(This, 0xE, offset + SCB_BLK_CTRL_SCB_CLK_ENABLE_SET); |
| write_vpu_reg(This, 0x7, offset + SCB_BLK_CTRL_CACHE_RESET_SET); |
| write_vpu_reg(This, 0x102, XMEM_CONTROL); |
| |
| read_data = read_vpu_reg(This, 0x70108); |
| vpu_dbg(LVL_IRQ, "%s read_data=%x\n", __func__, read_data); |
| } |
| |
| static void vpu_enc_reset(struct vpu_dev *This) |
| { |
| const off_t offset = SCB_XREG_SLV_BASE + SCB_SCB_BLK_CTRL; |
| |
| vpu_log_func(); |
| write_vpu_reg(This, 0x7, offset + SCB_BLK_CTRL_CACHE_RESET_CLR); |
| } |
| |
| static int vpu_enc_enable_hw(struct vpu_dev *This) |
| { |
| vpu_log_func(); |
| vpu_enc_setup(This); |
| |
| This->hw_enable = true; |
| |
| return 0; |
| } |
| |
| static void vpu_enc_disable_hw(struct vpu_dev *This) |
| { |
| This->hw_enable = false; |
| vpu_enc_reset(This); |
| if (This->regs_base) { |
| iounmap(This->regs_base); |
| This->regs_base = NULL; |
| } |
| } |
| |
| static int parse_core_info(struct core_device *core, struct device_node *np) |
| { |
| int ret; |
| u32 val; |
| |
| WARN_ON(!core || !np); |
| |
| ret = of_property_read_u32_index(np, "reg", 0, &val); |
| if (ret) { |
| vpu_err("find reg for core[%d] fail\n", core->id); |
| return ret; |
| } |
| core->reg_base = val; |
| |
| ret = of_property_read_u32_index(np, "reg", 1, &val); |
| if (ret) { |
| vpu_err("find reg for core[%d] fail\n", core->id); |
| return ret; |
| } |
| core->reg_size = val; |
| |
| ret = of_property_read_u32_index(np, "reg-csr", 0, &val); |
| if (ret) { |
| vpu_err("find reg-csr for core[%d] fail\n", core->id); |
| return ret; |
| } |
| core->reg_csr_base = val; |
| |
| ret = of_property_read_u32_index(np, "reg-csr", 1, &val); |
| if (ret) { |
| vpu_err("find reg-csr for core[%d] fail\n", core->id); |
| return ret; |
| } |
| core->reg_csr_size = val; |
| |
| ret = of_irq_get(np, 0); |
| if (ret < 0) { |
| vpu_err("get irq for core[%d] fail\n", core->id); |
| return -EINVAL; |
| } |
| core->irq = ret; |
| |
| ret = of_property_read_u32(np, "fsl,vpu_ap_mu_id", &val); |
| if (!ret) |
| core->vpu_mu_id = val; |
| |
| ret = of_property_read_u32(np, "fw-buf-size", &val); |
| if (ret) { |
| vpu_err("find fw-buf-size for core[%d] fail\n", core->id); |
| core->fw_buf_size = M0_BOOT_SIZE_DEFAULT; |
| } else { |
| core->fw_buf_size = val; |
| } |
| core->fw_buf_size = max_t(u32, core->fw_buf_size, M0_BOOT_SIZE_MIN); |
| |
| ret = of_property_read_u32(np, "rpc-buf-size", &val); |
| if (ret) { |
| vpu_err("find rpc-buf-size for core[%d] fail\n", core->id); |
| core->rpc_buf_size = RPC_SIZE_DEFAULT; |
| } else { |
| core->rpc_buf_size = val; |
| } |
| core->rpc_buf_size = max_t(u32, core->rpc_buf_size, RPC_SIZE_MIN); |
| |
| ret = of_property_read_u32(np, "print-buf-size", &val); |
| if (ret) { |
| vpu_err("find print-buf-size for core[%d] fail\n", core->id); |
| core->print_buf_size = PRINT_SIZE_DEFAULT; |
| } else { |
| core->print_buf_size = val; |
| } |
| core->print_buf_size = max_t(u32, core->print_buf_size, PRINT_SIZE_MIN); |
| |
| return 0; |
| } |
| |
| static int parse_dt_cores(struct vpu_dev *dev, struct device_node *np) |
| { |
| char core_name[64]; |
| struct device_node *node = NULL; |
| struct core_device *core = NULL; |
| int i; |
| int ret; |
| |
| dev->core_num = 0; |
| for (i = 0; i < VPU_ENC_MAX_CORE_NUM; i++) { |
| scnprintf(core_name, sizeof(core_name), "core%d", i); |
| node = of_find_node_by_name(np, core_name); |
| if (!node) { |
| vpu_dbg(LVL_INFO, "can't find %s\n", core_name); |
| break; |
| } |
| |
| core = &dev->core_dev[i]; |
| core->id = i; |
| ret = parse_core_info(core, node); |
| of_node_put(node); |
| node = NULL; |
| if (ret) { |
| vpu_err("parse core[%d] fail\n", i); |
| break; |
| } |
| } |
| if (i == 0) |
| return -EINVAL; |
| |
| dev->core_num = i; |
| |
| return 0; |
| } |
| |
| static int parse_dt_info(struct vpu_dev *dev, struct device_node *np) |
| { |
| int ret; |
| struct device_node *reserved_node = NULL; |
| struct resource reserved_fw; |
| struct resource reserved_rpc; |
| struct resource reserved_mem; |
| u32 fw_total_size = 0; |
| u32 rpc_total_size = 0; |
| u32 val; |
| u32 i; |
| |
| if (!dev || !np) |
| return -EINVAL; |
| |
| ret = of_property_read_u32_index(np, "reg-rpc-system", 0, &val); |
| if (ret) { |
| vpu_err("get reg-rpc-system fail\n"); |
| return -EINVAL; |
| } |
| dev->reg_rpc_system = val; |
| |
| reserved_node = of_parse_phandle(np, "boot-region", 0); |
| if (!reserved_node) { |
| vpu_err("error: boot-region of_parse_phandle error\n"); |
| return -ENODEV; |
| } |
| if (of_address_to_resource(reserved_node, 0, &reserved_fw)) { |
| vpu_err("error: boot-region of_address_to_resource error\n"); |
| return -EINVAL; |
| } |
| |
| reserved_node = of_parse_phandle(np, "rpc-region", 0); |
| if (!reserved_node) { |
| vpu_err("error: rpc-region of_parse_phandle error\n"); |
| return -ENODEV; |
| } |
| if (of_address_to_resource(reserved_node, 0, &reserved_rpc)) { |
| vpu_err("error: rpc-region of_address_to_resource error\n"); |
| return -EINVAL; |
| } |
| |
| reserved_node = of_parse_phandle(np, "reserved-region", 0); |
| if (!reserved_node) { |
| vpu_err("error: rpc-region of_parse_phandle error\n"); |
| return -ENODEV; |
| } |
| if (of_address_to_resource(reserved_node, 0, &reserved_mem)) { |
| vpu_err("error: rpc-region of_address_to_resource error\n"); |
| return -EINVAL; |
| } |
| dev->reserved_mem.phy_addr = reserved_mem.start; |
| dev->reserved_mem.size = resource_size(&reserved_mem); |
| |
| ret = parse_dt_cores(dev, np); |
| if (ret) { |
| vpu_err("parse cores from dt fail\n"); |
| return ret; |
| } |
| |
| fw_total_size = 0; |
| rpc_total_size = 0; |
| for (i = 0; i < dev->core_num; i++) { |
| struct core_device *core = &dev->core_dev[i]; |
| |
| core->m0_p_fw_space_phy = reserved_fw.start + fw_total_size; |
| core->m0_rpc_phy = reserved_rpc.start + rpc_total_size; |
| fw_total_size += core->fw_buf_size; |
| rpc_total_size += core->rpc_buf_size; |
| rpc_total_size += core->print_buf_size; |
| } |
| |
| if (fw_total_size > resource_size(&reserved_fw)) { |
| vpu_err("boot-region's size(0x%llx) is less than wanted:0x%x\n", |
| resource_size(&reserved_fw), fw_total_size); |
| return -EINVAL; |
| } |
| if (rpc_total_size > resource_size(&reserved_rpc)) { |
| vpu_err("rpc-region's size(0x%llx) is less than wanted:0x%x\n", |
| resource_size(&reserved_rpc), rpc_total_size); |
| return -EINVAL; |
| } |
| |
| dev->supported_size.min_width = VPU_ENC_WIDTH_MIN; |
| dev->supported_size.max_width = VPU_ENC_WIDTH_MAX; |
| dev->supported_size.step_width = VPU_ENC_WIDTH_STEP; |
| dev->supported_size.min_height = VPU_ENC_HEIGHT_MIN; |
| dev->supported_size.max_height = VPU_ENC_HEIGHT_MAX; |
| dev->supported_size.step_height = VPU_ENC_HEIGHT_STEP; |
| |
| dev->supported_fps.min = VPU_ENC_FRAMERATE_MIN; |
| dev->supported_fps.max = VPU_ENC_FRAMERATE_MAX; |
| dev->supported_fps.step = VPU_ENC_FRAMERATE_STEP; |
| |
| ret = of_property_read_u32_index(np, "resolution-max", 0, &val); |
| if (!ret) |
| dev->supported_size.max_width = val; |
| |
| ret = of_property_read_u32_index(np, "resolution-max", 1, &val); |
| if (!ret) |
| dev->supported_size.max_height = val; |
| |
| ret = of_property_read_u32_index(np, "fps-max", 0, &val); |
| if (!ret) |
| dev->supported_fps.max = val; |
| |
| return 0; |
| } |
| |
| static int create_vpu_video_device(struct vpu_dev *dev) |
| { |
| int ret; |
| |
| dev->pvpu_encoder_dev = video_device_alloc(); |
| if (!dev->pvpu_encoder_dev) { |
| vpu_err("alloc vpu encoder video device fail\n"); |
| return -ENOMEM; |
| } |
| |
| strlcpy(dev->pvpu_encoder_dev->name, |
| vpu_enc_v4l2_videodevice.name, |
| sizeof(dev->pvpu_encoder_dev->name)); |
| dev->pvpu_encoder_dev->fops = vpu_enc_v4l2_videodevice.fops; |
| dev->pvpu_encoder_dev->ioctl_ops = vpu_enc_v4l2_videodevice.ioctl_ops; |
| dev->pvpu_encoder_dev->release = video_device_release; |
| dev->pvpu_encoder_dev->vfl_dir = vpu_enc_v4l2_videodevice.vfl_dir; |
| dev->pvpu_encoder_dev->v4l2_dev = &dev->v4l2_dev; |
| video_set_drvdata(dev->pvpu_encoder_dev, dev); |
| |
| ret = video_register_device(dev->pvpu_encoder_dev, |
| VFL_TYPE_GRABBER, |
| ENCODER_NODE_NUMBER); |
| if (ret) { |
| vpu_err("unable to register video encoder device\n"); |
| video_device_release(dev->pvpu_encoder_dev); |
| dev->pvpu_encoder_dev = NULL; |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int init_vpu_attrs(struct core_device *core) |
| { |
| int i; |
| |
| WARN_ON(!core); |
| |
| for (i = 0; i < ARRAY_SIZE(core->attr); i++) { |
| struct vpu_attr *attr = &core->attr[i]; |
| |
| attr->core = core; |
| attr->index = i; |
| scnprintf(attr->name, sizeof(attr->name) - 1, "instance.%d.%d", |
| core->id, attr->index); |
| sysfs_attr_init(&attr->dev_attr.attr); |
| attr->dev_attr.attr.name = attr->name; |
| attr->dev_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444); |
| attr->dev_attr.show = show_instance_info; |
| |
| atomic64_set(&attr->total_dma_size, 0); |
| |
| attr->created = false; |
| } |
| |
| return 0; |
| } |
| |
| static int release_vpu_attrs(struct core_device *core) |
| { |
| int i; |
| |
| WARN_ON(!core); |
| |
| for (i = 0; i < ARRAY_SIZE(core->attr); i++) { |
| struct vpu_attr *attr = &core->attr[i]; |
| |
| if (!attr->created) |
| continue; |
| device_remove_file(attr->core->generic_dev, &attr->dev_attr); |
| } |
| |
| return 0; |
| } |
| |
| static int is_ctx_frozen(struct vpu_ctx *ctx) |
| { |
| int is_frozen = 1; |
| int i; |
| struct vpu_attr *attr = get_vpu_ctx_attr(ctx); |
| |
| for (i = 0; i < GTB_ENC_CMD_RESERVED; i++) { |
| if (attr->statistic.cmd[i] != ctx->sts.cmd[i]) |
| is_frozen = 0; |
| ctx->sts.cmd[i] = attr->statistic.cmd[i]; |
| } |
| |
| for (i = 0; i < VID_API_ENC_EVENT_RESERVED; i++) { |
| if (attr->statistic.event[i] != ctx->sts.event[i]) |
| is_frozen = 0; |
| ctx->sts.event[i] = attr->statistic.event[i]; |
| } |
| |
| if (ctx->sts.cmd[GTB_ENC_CMD_FRAME_ENCODE] == |
| ctx->sts.event[VID_API_ENC_EVENT_FRAME_DONE]) |
| is_frozen = 0; |
| |
| if (ctx->sts.cmd[GTB_ENC_CMD_CONFIGURE_CODEC] > |
| ctx->sts.event[VID_API_ENC_EVENT_MEM_REQUEST]) |
| is_frozen = 1; |
| if (ctx->sts.cmd[GTB_ENC_CMD_STREAM_START] > |
| ctx->sts.event[VID_API_ENC_EVENT_START_DONE]) |
| is_frozen = 1; |
| if (ctx->sts.cmd[GTB_ENC_CMD_STREAM_STOP] > |
| ctx->sts.event[VID_API_ENC_EVENT_STOP_DONE]) |
| is_frozen = 1; |
| |
| return is_frozen; |
| } |
| |
| static bool check_vpu_ctx_is_hang(struct vpu_ctx *ctx) |
| { |
| if (is_ctx_frozen(ctx)) |
| ctx->frozen_count++; |
| else |
| ctx->frozen_count = 0; |
| |
| if (ctx->frozen_count > VPU_ENC_HANG_THD) { |
| set_bit(VPU_ENC_STATUS_HANG, &ctx->status); |
| ctx->frozen_count = VPU_ENC_HANG_THD; |
| } else if (ctx->frozen_count == 0) { |
| clear_bit(VPU_ENC_STATUS_HANG, &ctx->status); |
| } |
| |
| if (test_bit(VPU_ENC_STATUS_HANG, &ctx->status)) |
| return true; |
| |
| return false; |
| } |
| |
| static void check_vpu_core_is_hang(struct core_device *core) |
| { |
| int i; |
| unsigned int instance_count = 0; |
| unsigned int hang_count = 0; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (!core->ctx[i]) |
| continue; |
| |
| if (check_vpu_ctx_is_hang(core->ctx[i])) |
| hang_count++; |
| instance_count++; |
| } |
| |
| if (!instance_count) |
| return; |
| if (hang_count == instance_count) |
| set_core_hang(core); |
| else |
| clear_core_hang(core); |
| } |
| |
| static void handle_vpu_core_watchdog(struct core_device *core) |
| { |
| if (!core->fw_is_ready) |
| return; |
| if (core->suspend) |
| return; |
| if (core->snapshot) |
| return; |
| |
| check_vpu_core_is_hang(core); |
| } |
| |
| static unsigned long get_timestamp_ns(struct timespec *ts) |
| { |
| if (!ts) |
| return 0; |
| |
| return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; |
| } |
| |
| static void calc_rt_fps(struct vpu_fps_sts *fps, |
| unsigned long number, struct timespec *ts) |
| { |
| unsigned long delta_num; |
| unsigned long delta_ts; |
| |
| if (!fps || !ts) |
| return; |
| |
| fps->times++; |
| if (fps->times < fps->thd) |
| return; |
| |
| if (number >= fps->frame_number) { |
| delta_num = number - fps->frame_number; |
| delta_ts = get_timestamp_ns(ts) - get_timestamp_ns(&fps->ts); |
| if (!delta_ts) |
| return; |
| fps->fps = delta_num * NSEC_PER_SEC * VPU_FPS_COEF / delta_ts; |
| } |
| fps->times = 0; |
| if (fps->thd) { |
| fps->frame_number = number; |
| memcpy(&fps->ts, ts, sizeof(fps->ts)); |
| } |
| } |
| |
| static void statistic_fps_info(struct vpu_statistic *sts) |
| { |
| unsigned long encoded_count = sts->encoded_count; |
| struct timespec ts; |
| int i; |
| |
| if (!sts->fps_sts_enable) |
| return; |
| getrawmonotonic(&ts); |
| for (i = 0; i < VPU_FPS_STS_CNT; i++) |
| calc_rt_fps(&sts->fps[i], encoded_count, &ts); |
| } |
| |
| static void print_firmware_debug(char *ptr, u32 size) |
| { |
| u32 total = 0; |
| u32 len; |
| |
| while (total < size) { |
| len = min_t(u32, size - total, 256); |
| |
| pr_info("%.*s", len, ptr + total); |
| total += len; |
| } |
| } |
| |
| static void handle_core_firmware_debug(struct core_device *core) |
| { |
| char *ptr; |
| u32 rptr; |
| u32 wptr; |
| |
| if (!core || !core->print_buf) |
| return; |
| |
| if (!test_bit(core->id, &debug_firmware_bitmap)) |
| return; |
| |
| rptr = core->print_buf->read; |
| wptr = core->print_buf->write; |
| if (rptr == wptr) |
| return; |
| |
| ptr = core->print_buf->buffer; |
| pr_info("----mem_printf for VPU Encoder core %d----\n", core->id); |
| if (rptr > wptr) { |
| print_firmware_debug(ptr + rptr, core->print_buf->bytes - rptr); |
| rptr = 0; |
| } |
| if (rptr < wptr) { |
| print_firmware_debug(ptr + rptr, wptr - rptr); |
| rptr = wptr; |
| } |
| if (rptr >= core->print_buf->bytes) |
| rptr = 0; |
| core->print_buf->read = rptr; |
| } |
| |
| static void handle_core_minors(struct core_device *core) |
| { |
| int i; |
| |
| for (i = 0; i < core->supported_instance_count; i++) |
| statistic_fps_info(&core->attr[i].statistic); |
| |
| handle_core_firmware_debug(core); |
| } |
| |
| static void vpu_enc_watchdog_handler(struct work_struct *work) |
| { |
| struct delayed_work *dwork; |
| struct vpu_dev *vdev; |
| int i; |
| |
| if (!work) |
| return; |
| |
| dwork = to_delayed_work(work); |
| vdev = container_of(dwork, struct vpu_dev, watchdog); |
| |
| mutex_lock(&vdev->dev_mutex); |
| for (i = 0; i < vdev->core_num; i++) |
| handle_vpu_core_watchdog(&vdev->core_dev[i]); |
| mutex_unlock(&vdev->dev_mutex); |
| |
| for (i = 0; i < vdev->core_num; i++) |
| handle_core_minors(&vdev->core_dev[i]); |
| |
| vdev->heartbeat++; |
| schedule_delayed_work(&vdev->watchdog, |
| msecs_to_jiffies(VPU_WATCHDOG_INTERVAL_MS)); |
| } |
| |
| static int init_vpu_core_dev(struct core_device *core_dev) |
| { |
| int ret; |
| |
| if (!core_dev) |
| return -EINVAL; |
| |
| mutex_init(&core_dev->cmd_mutex); |
| init_completion(&core_dev->start_cmp); |
| init_completion(&core_dev->snap_done_cmp); |
| |
| core_dev->workqueue = alloc_workqueue("vpu", |
| WQ_UNBOUND | WQ_MEM_RECLAIM, 1); |
| if (!core_dev->workqueue) { |
| vpu_err("%s unable to alloc workqueue\n", __func__); |
| ret = -ENOMEM; |
| return ret; |
| } |
| |
| INIT_WORK(&core_dev->msg_work, vpu_enc_msg_run_work); |
| |
| ret = vpu_enc_mu_init(core_dev); |
| if (ret) { |
| vpu_err("%s vpu mu init failed\n", __func__); |
| goto error; |
| } |
| //firmware space for M0 |
| core_dev->m0_p_fw_space_vir = |
| ioremap_wc(core_dev->m0_p_fw_space_phy, core_dev->fw_buf_size); |
| if (!core_dev->m0_p_fw_space_vir) |
| vpu_err("failed to remap space for M0 firmware\n"); |
| |
| cleanup_firmware_memory(core_dev); |
| |
| core_dev->m0_rpc_virt = |
| ioremap_wc(core_dev->m0_rpc_phy, |
| core_dev->rpc_buf_size + core_dev->print_buf_size); |
| if (!core_dev->m0_rpc_virt) |
| vpu_err("failed to remap space for shared memory\n"); |
| |
| memset_io(core_dev->m0_rpc_virt, 0, core_dev->rpc_buf_size); |
| |
| reset_vpu_core_dev(core_dev); |
| |
| init_vpu_attrs(core_dev); |
| |
| scnprintf(core_dev->name, sizeof(core_dev->name) - 1, |
| "core.%d", core_dev->id); |
| sysfs_attr_init(&core_dev->core_attr.attr); |
| core_dev->core_attr.attr.name = core_dev->name; |
| core_dev->core_attr.attr.mode = VERIFY_OCTAL_PERMISSIONS(0444); |
| core_dev->core_attr.show = show_core_info; |
| device_create_file(core_dev->generic_dev, &core_dev->core_attr); |
| |
| return 0; |
| error: |
| if (core_dev->workqueue) { |
| destroy_workqueue(core_dev->workqueue); |
| core_dev->workqueue = NULL; |
| } |
| return ret; |
| } |
| |
| static int uninit_vpu_core_dev(struct core_device *core_dev) |
| { |
| if (!core_dev) |
| return -EINVAL; |
| |
| if (core_dev->core_attr.attr.name) |
| device_remove_file(core_dev->generic_dev, &core_dev->core_attr); |
| release_vpu_attrs(core_dev); |
| if (core_dev->workqueue) { |
| cancel_work_sync(&core_dev->msg_work); |
| destroy_workqueue(core_dev->workqueue); |
| core_dev->workqueue = NULL; |
| } |
| |
| if (core_dev->m0_p_fw_space_vir) { |
| iounmap(core_dev->m0_p_fw_space_vir); |
| core_dev->m0_p_fw_space_vir = NULL; |
| } |
| core_dev->m0_p_fw_space_phy = 0; |
| |
| if (core_dev->m0_rpc_virt) { |
| iounmap(core_dev->m0_rpc_virt); |
| core_dev->m0_rpc_virt = NULL; |
| } |
| core_dev->m0_rpc_phy = 0; |
| |
| if (core_dev->mu_base_virtaddr) |
| core_dev->mu_base_virtaddr = NULL; |
| |
| if (core_dev->generic_dev) { |
| put_device(core_dev->generic_dev); |
| core_dev->generic_dev = NULL; |
| } |
| |
| return 0; |
| } |
| |
| static void init_vpu_enc_watchdog(struct vpu_dev *vdev) |
| { |
| if (!vdev) |
| return; |
| |
| INIT_DELAYED_WORK(&vdev->watchdog, vpu_enc_watchdog_handler); |
| schedule_delayed_work(&vdev->watchdog, |
| msecs_to_jiffies(VPU_WATCHDOG_INTERVAL_MS)); |
| } |
| |
| static int check_vpu_encoder_is_available(void) |
| { |
| sc_ipc_t mu_ipc; |
| sc_ipc_id_t mu_id; |
| uint32_t fuse = 0xffff; |
| int ret; |
| |
| ret = sc_ipc_getMuID(&mu_id); |
| if (ret) { |
| vpu_err("sc_ipc_getMuID() can't obtain mu id SCI! %d\n", |
| ret); |
| return -EINVAL; |
| } |
| |
| ret = sc_ipc_open(&mu_ipc, mu_id); |
| if (ret) { |
| vpu_err("sc_ipc_getMuID() can't open MU channel to SCU! %d\n", |
| ret); |
| return -EINVAL; |
| } |
| |
| ret = sc_misc_otp_fuse_read(mu_ipc, VPU_DISABLE_BITS, &fuse); |
| sc_ipc_close(mu_ipc); |
| if (ret) { |
| vpu_err("sc_misc_otp_fuse_read fail! %d\n", ret); |
| return -EINVAL; |
| } |
| |
| vpu_dbg(LVL_INFO, "mu_id = %d, fuse[7] = 0x%x\n", mu_id, fuse); |
| if (fuse & VPU_ENCODER_MASK) { |
| vpu_err("----Error, VPU Encoder is disabled\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct of_device_id vpu_enc_of_match[]; |
| static int vpu_enc_probe(struct platform_device *pdev) |
| { |
| struct vpu_dev *dev; |
| struct device_node *np = pdev->dev.of_node; |
| const struct of_device_id *dev_id = NULL; |
| struct resource *res = NULL; |
| u_int32 i; |
| int ret; |
| |
| if (!np) { |
| vpu_err("error: %s of_node is NULL\n", __func__); |
| return -EINVAL; |
| } |
| |
| dev_id = of_match_device(vpu_enc_of_match, &pdev->dev); |
| if (!dev_id) { |
| vpu_err("unmatch vpu encoder device\n"); |
| return -EINVAL; |
| } |
| vpu_dbg(LVL_INFO, "probe %s\n", dev_id->compatible); |
| |
| if (check_vpu_encoder_is_available()) |
| return -EINVAL; |
| |
| dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); |
| if (!dev) |
| return -ENOMEM; |
| dev->plat_type = *(enum PLAT_TYPE *)dev_id->data; |
| dev->plat_dev = pdev; |
| dev->generic_dev = get_device(&pdev->dev); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) |
| goto error_put_dev; |
| |
| dev->reg_vpu_base = res->start; |
| dev->reg_vpu_size = resource_size(res); |
| |
| ret = parse_dt_info(dev, np); |
| if (ret) { |
| vpu_err("parse device tree fail\n"); |
| goto error_put_dev; |
| } |
| |
| dev->regs_base = ioremap(dev->reg_vpu_base, dev->reg_vpu_size); |
| if (!dev->regs_base) { |
| vpu_err("%s could not map regs_base\n", __func__); |
| ret = PTR_ERR(dev->regs_base); |
| goto error_put_dev; |
| } |
| |
| ret = vpu_enc_init_reserved_memory(&dev->reserved_mem); |
| if (ret) { |
| vpu_err("%s couldn't init reserved memory\n", __func__); |
| goto error_iounmap; |
| } |
| |
| ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); |
| if (ret) { |
| vpu_err("%s unable to register v4l2 dev\n", __func__); |
| goto error_reserved_mem; |
| } |
| |
| platform_set_drvdata(pdev, dev); |
| |
| ret = create_vpu_video_device(dev); |
| if (ret) { |
| vpu_err("create vpu video device fail\n"); |
| goto error_unreg_v4l2; |
| } |
| |
| pm_runtime_enable(&pdev->dev); |
| pm_runtime_get_sync(&pdev->dev); |
| |
| vpu_enc_enable_hw(dev); |
| |
| mutex_init(&dev->dev_mutex); |
| for (i = 0; i < dev->core_num; i++) { |
| dev->core_dev[i].id = i; |
| dev->core_dev[i].generic_dev = get_device(dev->generic_dev); |
| dev->core_dev[i].vdev = dev; |
| ret = init_vpu_core_dev(&dev->core_dev[i]); |
| if (ret) |
| break; |
| } |
| if (i == 0) |
| goto error_init_core; |
| dev->core_num = i; |
| |
| pm_runtime_put_sync(&pdev->dev); |
| |
| device_create_file(&pdev->dev, &dev_attr_meminfo); |
| device_create_file(&pdev->dev, &dev_attr_buffer); |
| device_create_file(&pdev->dev, &dev_attr_fpsinfo); |
| device_create_file(&pdev->dev, &dev_attr_vpuinfo); |
| init_vpu_enc_watchdog(dev); |
| vpu_dbg(LVL_INFO, "VPU Encoder registered\n"); |
| |
| return 0; |
| |
| error_init_core: |
| for (i = 0; i < dev->core_num; i++) |
| uninit_vpu_core_dev(&dev->core_dev[i]); |
| |
| vpu_enc_disable_hw(dev); |
| pm_runtime_put_sync(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| |
| if (dev->pvpu_encoder_dev) { |
| video_unregister_device(dev->pvpu_encoder_dev); |
| dev->pvpu_encoder_dev = NULL; |
| } |
| error_unreg_v4l2: |
| v4l2_device_unregister(&dev->v4l2_dev); |
| error_reserved_mem: |
| vpu_enc_release_reserved_memory(&dev->reserved_mem); |
| error_iounmap: |
| if (dev->regs_base) { |
| iounmap(dev->regs_base); |
| dev->regs_base = NULL; |
| } |
| error_put_dev: |
| if (dev->generic_dev) { |
| put_device(dev->generic_dev); |
| dev->generic_dev = NULL; |
| } |
| return -EINVAL; |
| } |
| |
| static int vpu_enc_remove(struct platform_device *pdev) |
| { |
| struct vpu_dev *dev = platform_get_drvdata(pdev); |
| u_int32 i; |
| |
| cancel_delayed_work_sync(&dev->watchdog); |
| device_remove_file(&pdev->dev, &dev_attr_vpuinfo); |
| device_remove_file(&pdev->dev, &dev_attr_fpsinfo); |
| device_remove_file(&pdev->dev, &dev_attr_buffer); |
| device_remove_file(&pdev->dev, &dev_attr_meminfo); |
| |
| pm_runtime_get_sync(&pdev->dev); |
| for (i = 0; i < dev->core_num; i++) |
| uninit_vpu_core_dev(&dev->core_dev[i]); |
| |
| vpu_enc_disable_hw(dev); |
| pm_runtime_put_sync(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| |
| if (video_get_drvdata(dev->pvpu_encoder_dev)) |
| video_unregister_device(dev->pvpu_encoder_dev); |
| |
| v4l2_device_unregister(&dev->v4l2_dev); |
| vpu_enc_release_reserved_memory(&dev->reserved_mem); |
| if (dev->regs_base) { |
| iounmap(dev->regs_base); |
| dev->regs_base = NULL; |
| } |
| if (dev->generic_dev) { |
| put_device(dev->generic_dev); |
| dev->generic_dev = NULL; |
| } |
| |
| vpu_dbg(LVL_INFO, "VPU Encoder removed\n"); |
| |
| return 0; |
| } |
| |
| static int vpu_enc_runtime_suspend(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int vpu_enc_runtime_resume(struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int is_core_activated(struct core_device *core) |
| { |
| WARN_ON(!core); |
| |
| if (readl_relaxed(core->mu_base_virtaddr + MU_B0_REG_CONTROL) == 0) |
| return false; |
| else |
| return true; |
| } |
| |
| static int is_need_shapshot(struct vpu_ctx *ctx) |
| { |
| if (!test_bit(VPU_ENC_STATUS_INITIALIZED, &ctx->status)) |
| return 0; |
| if (!test_bit(VPU_ENC_STATUS_CONFIGURED, &ctx->status)) |
| return 0; |
| if (test_bit(VPU_ENC_STATUS_CLOSED, &ctx->status)) |
| return 0; |
| if (test_bit(VPU_ENC_STATUS_STOP_SEND, &ctx->status)) |
| return 0; |
| if (test_bit(VPU_ENC_STATUS_STOP_DONE, &ctx->status)) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int vpu_enc_snapshot(struct vpu_ctx *ctx) |
| { |
| int ret; |
| |
| if (!ctx) |
| return -EINVAL; |
| |
| vpu_dbg(LVL_INFO, "core[%d] snapshot\n", ctx->core_dev->id); |
| vpu_ctx_send_cmd(ctx, GTB_ENC_CMD_SNAPSHOT, 0, NULL); |
| ret = wait_for_completion_timeout(&ctx->core_dev->snap_done_cmp, |
| msecs_to_jiffies(1000)); |
| if (!ret) |
| vpu_err("error:wait for snapdone event timeout!\n"); |
| else |
| ctx->core_dev->snapshot = true; |
| |
| return 0; |
| } |
| |
| static int resume_from_snapshot(struct core_device *core) |
| { |
| int ret = 0; |
| |
| if (!core) |
| return -EINVAL; |
| if (!core->snapshot) |
| return 0; |
| |
| vpu_dbg(LVL_INFO, "core[%d] resume from snapshot\n", core->id); |
| |
| init_completion(&core->start_cmp); |
| set_vpu_fw_addr(core->vdev, core); |
| ret = wait_for_start_done(core, 1); |
| if (ret) { |
| set_core_force_release(core); |
| reset_vpu_core_dev(core); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int re_download_firmware(struct core_device *core) |
| { |
| if (!core) |
| return -EINVAL; |
| |
| vpu_dbg(LVL_INFO, "re download firmware for core[%d]\n", core->id); |
| |
| reset_vpu_core_dev(core); |
| return download_vpu_firmware(core->vdev, core); |
| } |
| |
| static int suspend_instance(struct vpu_ctx *ctx) |
| { |
| int ret = 0; |
| |
| if (!ctx) |
| return 0; |
| |
| if (test_bit(VPU_ENC_STATUS_STOP_REQ, &ctx->status) || |
| test_bit(VPU_ENC_STATUS_STOP_SEND, &ctx->status)) |
| wait_for_stop_done(ctx); |
| |
| mutex_lock(&ctx->instance_mutex); |
| if (!ctx->core_dev->snapshot && is_need_shapshot(ctx)) |
| ret = vpu_enc_snapshot(ctx); |
| set_bit(VPU_ENC_STATUS_SNAPSHOT, &ctx->status); |
| mutex_unlock(&ctx->instance_mutex); |
| |
| return ret; |
| } |
| |
| static int resume_instance(struct vpu_ctx *ctx) |
| { |
| if (!ctx) |
| return 0; |
| |
| clear_bit(VPU_ENC_STATUS_SNAPSHOT, &ctx->status); |
| |
| return 0; |
| } |
| |
| static int suspend_core(struct core_device *core) |
| { |
| int i; |
| int ret = 0; |
| |
| WARN_ON(!core); |
| |
| core->snapshot = false; |
| |
| if (!core->fw_is_ready) |
| return 0; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (!core->ctx[i]) |
| continue; |
| ret = suspend_instance(core->ctx[i]); |
| if (ret) |
| return ret; |
| } |
| |
| for (i = 0; i < core->supported_instance_count; i++) |
| vpu_ctx_power_off(core->ctx[i]); |
| |
| core->suspend = true; |
| |
| return 0; |
| } |
| |
| static int resume_core(struct core_device *core) |
| { |
| int ret = 0; |
| u32 instance_count = 0; |
| int i; |
| |
| WARN_ON(!core); |
| |
| if (!core->suspend) |
| return 0; |
| |
| for (i = 0; i < core->supported_instance_count; i++) { |
| if (!core->ctx[i]) |
| continue; |
| instance_count++; |
| vpu_ctx_power_on(core->ctx[i]); |
| resume_instance(core->ctx[i]); |
| } |
| |
| /* if the core isn't activated, it means it has been power off and on */ |
| if (!is_core_activated(core)) { |
| if (!core->vdev->hw_enable) |
| vpu_enc_enable_hw(core->vdev); |
| if (core->snapshot) |
| ret = resume_from_snapshot(core); |
| else if (instance_count) |
| ret = re_download_firmware(core); |
| else |
| reset_vpu_core_dev(core); |
| } else { |
| if (core->snapshot || instance_count) |
| ret = sw_reset_firmware(core, 1); |
| } |
| |
| core->snapshot = false; |
| core->suspend = false; |
| |
| return ret; |
| } |
| |
| static int vpu_enc_suspend(struct device *dev) |
| { |
| struct vpu_dev *vpudev = (struct vpu_dev *)dev_get_drvdata(dev); |
| int i; |
| int ret = 0; |
| |
| vpu_dbg(LVL_INFO, "suspend\n"); |
| |
| mutex_lock(&vpudev->dev_mutex); |
| pm_runtime_get_sync(dev); |
| for (i = 0; i < vpudev->core_num; i++) { |
| ret = suspend_core(&vpudev->core_dev[i]); |
| if (ret) |
| break; |
| } |
| pm_runtime_put_sync(dev); |
| mutex_unlock(&vpudev->dev_mutex); |
| |
| vpu_dbg(LVL_INFO, "suspend done\n"); |
| |
| return ret; |
| } |
| |
| static int vpu_enc_resume(struct device *dev) |
| { |
| struct vpu_dev *vpudev = (struct vpu_dev *)dev_get_drvdata(dev); |
| int i; |
| int ret = 0; |
| |
| vpu_dbg(LVL_INFO, "resume\n"); |
| |
| mutex_lock(&vpudev->dev_mutex); |
| pm_runtime_get_sync(dev); |
| vpudev->hw_enable = false; |
| for (i = 0; i < vpudev->core_num; i++) { |
| ret = resume_core(&vpudev->core_dev[i]); |
| if (ret) |
| break; |
| } |
| vpudev->hw_enable = true; |
| pm_runtime_put_sync(dev); |
| mutex_unlock(&vpudev->dev_mutex); |
| |
| vpu_dbg(LVL_INFO, "resume done\n"); |
| |
| return ret; |
| } |
| |
| static const struct dev_pm_ops vpu_enc_pm_ops = { |
| SET_RUNTIME_PM_OPS(vpu_enc_runtime_suspend, vpu_enc_runtime_resume, NULL) |
| SET_SYSTEM_SLEEP_PM_OPS(vpu_enc_suspend, vpu_enc_resume) |
| }; |
| |
| static enum PLAT_TYPE supported_plat_types[PLAT_TYPE_RESERVED] = { |
| [IMX8QXP] = IMX8QXP, |
| [IMX8QM] = IMX8QM, |
| }; |
| |
| static const struct of_device_id vpu_enc_of_match[] = { |
| { .compatible = "nxp,imx8qm-b0-vpuenc", |
| .data = (void *)&supported_plat_types[IMX8QM] |
| }, |
| { .compatible = "nxp,imx8qxp-b0-vpuenc", |
| .data = (void *)&supported_plat_types[IMX8QXP] |
| }, |
| {} |
| } |
| MODULE_DEVICE_TABLE(of, vpu_enc_of_match); |
| |
| static struct platform_driver vpu_enc_driver = { |
| .probe = vpu_enc_probe, |
| .remove = vpu_enc_remove, |
| .driver = { |
| .name = "vpu-b0-encoder", |
| .of_match_table = vpu_enc_of_match, |
| .pm = &vpu_enc_pm_ops, |
| }, |
| }; |
| module_platform_driver(vpu_enc_driver); |
| |
| MODULE_AUTHOR("Freescale Semiconductor, Inc."); |
| MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION(VPU_ENC_DRIVER_VERSION); |
| |
| module_param(vpu_dbg_level_encoder, int, 0644); |
| MODULE_PARM_DESC(vpu_dbg_level_encoder, "Debug level (0-4)"); |
| |
| module_param(reset_on_hang, int, 0644); |
| MODULE_PARM_DESC(reset_on_hang, "reset on hang (0-1)"); |
| |
| module_param(show_detail_index, int, 0644); |
| MODULE_PARM_DESC(show_detail_index, "show memory detail info index"); |
| |
| module_param(debug_firmware_bitmap, long, 0644); |
| MODULE_PARM_DESC(debug_firmware_bitmap, "firmware debug info switch"); |
| |