/* * Support for Medifield PNW Camera Imaging ISP subsystem. * * Copyright (c) 2010 Intel Corporation. All Rights Reserved. * * Copyright (c) 2010 Silicon Hive www.siliconhive.com. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * */ #include #include #include #include #include "atomisp_cmd.h" #include "atomisp_common.h" #include "atomisp_fops.h" #include "atomisp_internal.h" #include "atomisp_ioctl.h" #include "atomisp_compat.h" #include "atomisp_subdev.h" #include "atomisp_v4l2.h" #include "atomisp-regs.h" #include "hmm/hmm.h" #include "hrt/hive_isp_css_mm_hrt.h" #include "type_support.h" #include "device_access/device_access.h" #include "memory_access/memory_access.h" #include "atomisp_acc.h" #define ISP_LEFT_PAD 128 /* equal to 2*NWAY */ /* * input image data, and current frame resolution for test */ #define ISP_PARAM_MMAP_OFFSET 0xfffff000 #define MAGIC_CHECK(is, should) \ do { \ if (unlikely((is) != (should))) { \ pr_err("magic mismatch: %x (expected %x)\n", \ is, should); \ BUG(); \ } \ } while (0) /* * Videobuf ops */ static int atomisp_buf_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) { struct atomisp_video_pipe *pipe = vq->priv_data; *size = pipe->pix.sizeimage; return 0; } static int atomisp_buf_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, enum v4l2_field field) { struct atomisp_video_pipe *pipe = vq->priv_data; vb->size = pipe->pix.sizeimage; vb->width = pipe->pix.width; vb->height = pipe->pix.height; vb->field = field; vb->state = VIDEOBUF_PREPARED; return 0; } static int atomisp_q_one_metadata_buffer(struct atomisp_sub_device *asd, enum atomisp_input_stream_id stream_id, enum atomisp_css_pipe_id css_pipe_id) { struct atomisp_metadata_buf *metadata_buf; enum atomisp_metadata_type md_type = atomisp_get_metadata_type(asd, css_pipe_id); struct list_head *metadata_list; if (asd->metadata_bufs_in_css[stream_id][css_pipe_id] >= ATOMISP_CSS_Q_DEPTH) return 0; /* we have reached CSS queue depth */ if (!list_empty(&asd->metadata[md_type])) { metadata_list = &asd->metadata[md_type]; } else if (!list_empty(&asd->metadata_ready[md_type])) { metadata_list = &asd->metadata_ready[md_type]; } else { dev_warn(asd->isp->dev, "%s: No metadata buffers available for type %d!\n", __func__, md_type); return -EINVAL; } metadata_buf = list_entry(metadata_list->next, struct atomisp_metadata_buf, list); list_del_init(&metadata_buf->list); if (atomisp_q_metadata_buffer_to_css(asd, metadata_buf, stream_id, css_pipe_id)) { list_add(&metadata_buf->list, metadata_list); return -EINVAL; } else { list_add_tail(&metadata_buf->list, &asd->metadata_in_css[md_type]); } asd->metadata_bufs_in_css[stream_id][css_pipe_id]++; return 0; } static int atomisp_q_one_s3a_buffer(struct atomisp_sub_device *asd, enum atomisp_input_stream_id stream_id, enum atomisp_css_pipe_id css_pipe_id) { struct atomisp_s3a_buf *s3a_buf; struct list_head *s3a_list; unsigned int exp_id; if (asd->s3a_bufs_in_css[css_pipe_id] >= ATOMISP_CSS_Q_DEPTH) return 0; /* we have reached CSS queue depth */ if (!list_empty(&asd->s3a_stats)) { s3a_list = &asd->s3a_stats; } else if (!list_empty(&asd->s3a_stats_ready)) { s3a_list = &asd->s3a_stats_ready; } else { dev_warn(asd->isp->dev, "%s: No s3a buffers available!\n", __func__); return -EINVAL; } s3a_buf = list_entry(s3a_list->next, struct atomisp_s3a_buf, list); list_del_init(&s3a_buf->list); exp_id = s3a_buf->s3a_data->exp_id; hmm_flush_vmap(s3a_buf->s3a_data->data_ptr); if (atomisp_q_s3a_buffer_to_css(asd, s3a_buf, stream_id, css_pipe_id)) { /* got from head, so return back to the head */ list_add(&s3a_buf->list, s3a_list); return -EINVAL; } else { list_add_tail(&s3a_buf->list, &asd->s3a_stats_in_css); if (s3a_list == &asd->s3a_stats_ready) dev_warn(asd->isp->dev, "%s: drop one s3a stat which has exp_id %d!\n", __func__, exp_id); } asd->s3a_bufs_in_css[css_pipe_id]++; return 0; } static int atomisp_q_one_dis_buffer(struct atomisp_sub_device *asd, enum atomisp_input_stream_id stream_id, enum atomisp_css_pipe_id css_pipe_id) { struct atomisp_dis_buf *dis_buf; unsigned long irqflags; if (asd->dis_bufs_in_css >= ATOMISP_CSS_Q_DEPTH) return 0; /* we have reached CSS queue depth */ spin_lock_irqsave(&asd->dis_stats_lock, irqflags); if (list_empty(&asd->dis_stats)) { spin_unlock_irqrestore(&asd->dis_stats_lock, irqflags); dev_warn(asd->isp->dev, "%s: No dis buffers available!\n", __func__); return -EINVAL; } dis_buf = list_entry(asd->dis_stats.prev, struct atomisp_dis_buf, list); list_del_init(&dis_buf->list); spin_unlock_irqrestore(&asd->dis_stats_lock, irqflags); hmm_flush_vmap(dis_buf->dis_data->data_ptr); if (atomisp_q_dis_buffer_to_css(asd, dis_buf, stream_id, css_pipe_id)) { spin_lock_irqsave(&asd->dis_stats_lock, irqflags); /* got from tail, so return back to the tail */ list_add_tail(&dis_buf->list, &asd->dis_stats); spin_unlock_irqrestore(&asd->dis_stats_lock, irqflags); return -EINVAL; } else { spin_lock_irqsave(&asd->dis_stats_lock, irqflags); list_add_tail(&dis_buf->list, &asd->dis_stats_in_css); spin_unlock_irqrestore(&asd->dis_stats_lock, irqflags); } asd->dis_bufs_in_css++; return 0; } int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, struct atomisp_video_pipe *pipe, enum atomisp_input_stream_id stream_id, enum atomisp_css_buffer_type css_buf_type, enum atomisp_css_pipe_id css_pipe_id) { struct videobuf_vmalloc_memory *vm_mem; struct atomisp_css_params_with_list *param; struct atomisp_css_dvs_grid_info *dvs_grid = atomisp_css_get_dvs_grid_info(&asd->params.curr_grid_info); unsigned long irqflags; int err = 0; while (pipe->buffers_in_css < ATOMISP_CSS_Q_DEPTH) { struct videobuf_buffer *vb; spin_lock_irqsave(&pipe->irq_lock, irqflags); if (list_empty(&pipe->activeq)) { spin_unlock_irqrestore(&pipe->irq_lock, irqflags); return -EINVAL; } vb = list_entry(pipe->activeq.next, struct videobuf_buffer, queue); list_del_init(&vb->queue); vb->state = VIDEOBUF_ACTIVE; spin_unlock_irqrestore(&pipe->irq_lock, irqflags); /* * If there is a per_frame setting to apply on the buffer, * do it before buffer en-queueing. */ vm_mem = vb->priv; param = pipe->frame_params[vb->i]; if (param) { atomisp_makeup_css_parameters(asd, &asd->params.css_param.update_flag, ¶m->params); atomisp_apply_css_parameters(asd, ¶m->params); if (param->params.update_flag.dz_config && asd->run_mode->val != ATOMISP_RUN_MODE_VIDEO) { err = atomisp_calculate_real_zoom_region(asd, ¶m->params.dz_config, css_pipe_id); if (!err) atomisp_css_set_dz_config(asd, ¶m->params.dz_config); } atomisp_css_set_isp_config_applied_frame(asd, vm_mem->vaddr); atomisp_css_update_isp_params_on_pipe(asd, asd->stream_env[stream_id].pipes[css_pipe_id]); asd->params.dvs_6axis = (struct atomisp_css_dvs_6axis *) param->params.dvs_6axis; /* * WORKAROUND: * Because the camera halv3 can't ensure to set zoom * region to per_frame setting and global setting at * same time and only set zoom region to pre_frame * setting now.so when the pre_frame setting inculde * zoom region,I will set it to global setting. */ if (param->params.update_flag.dz_config && asd->run_mode->val != ATOMISP_RUN_MODE_VIDEO && !err) { memcpy(&asd->params.css_param.dz_config, ¶m->params.dz_config, sizeof(struct ia_css_dz_config)); asd->params.css_param.update_flag.dz_config = (struct atomisp_dz_config *) &asd->params.css_param.dz_config; asd->params.css_update_params_needed = true; } } /* Enqueue buffer */ err = atomisp_q_video_buffer_to_css(asd, vm_mem, stream_id, css_buf_type, css_pipe_id); if (err) { spin_lock_irqsave(&pipe->irq_lock, irqflags); list_add_tail(&vb->queue, &pipe->activeq); vb->state = VIDEOBUF_QUEUED; spin_unlock_irqrestore(&pipe->irq_lock, irqflags); dev_err(asd->isp->dev, "%s, css q fails: %d\n", __func__, err); return -EINVAL; } pipe->buffers_in_css++; /* enqueue 3A/DIS/metadata buffers */ if (asd->params.curr_grid_info.s3a_grid.enable && css_pipe_id == asd->params.s3a_enabled_pipe && css_buf_type == CSS_BUFFER_TYPE_OUTPUT_FRAME) atomisp_q_one_s3a_buffer(asd, stream_id, css_pipe_id); if (asd->stream_env[ATOMISP_INPUT_STREAM_GENERAL].stream_info. metadata_info.size && css_buf_type == CSS_BUFFER_TYPE_OUTPUT_FRAME) atomisp_q_one_metadata_buffer(asd, stream_id, css_pipe_id); if (dvs_grid && dvs_grid->enable && css_pipe_id == CSS_PIPE_ID_VIDEO && css_buf_type == CSS_BUFFER_TYPE_OUTPUT_FRAME) atomisp_q_one_dis_buffer(asd, stream_id, css_pipe_id); } return 0; } static int atomisp_get_css_buf_type(struct atomisp_sub_device *asd, enum atomisp_css_pipe_id pipe_id, uint16_t source_pad) { if (ATOMISP_USE_YUVPP(asd)) { /* when run ZSL case */ if (asd->continuous_mode->val && asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE) return CSS_BUFFER_TYPE_OUTPUT_FRAME; else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW) return CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME; else return CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; } /*when run SDV case*/ if (asd->continuous_mode->val && asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE) return CSS_BUFFER_TYPE_OUTPUT_FRAME; else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW) return CSS_BUFFER_TYPE_SEC_VF_OUTPUT_FRAME; else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_VIDEO) return CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME; else return CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; } /*other case: default setting*/ if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE || source_pad == ATOMISP_SUBDEV_PAD_SOURCE_VIDEO || (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW && asd->run_mode->val != ATOMISP_RUN_MODE_VIDEO)) return CSS_BUFFER_TYPE_OUTPUT_FRAME; else return CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; } if (pipe_id == CSS_PIPE_ID_COPY || source_pad == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE || source_pad == ATOMISP_SUBDEV_PAD_SOURCE_VIDEO || (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW && asd->run_mode->val != ATOMISP_RUN_MODE_VIDEO)) return CSS_BUFFER_TYPE_OUTPUT_FRAME; else return CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; } static int atomisp_qbuffers_to_css_for_all_pipes(struct atomisp_sub_device *asd) { enum atomisp_css_buffer_type buf_type; enum atomisp_css_pipe_id css_capture_pipe_id = CSS_PIPE_ID_COPY; enum atomisp_css_pipe_id css_preview_pipe_id = CSS_PIPE_ID_COPY; enum atomisp_css_pipe_id css_video_pipe_id = CSS_PIPE_ID_COPY; enum atomisp_input_stream_id input_stream_id; struct atomisp_video_pipe *capture_pipe; struct atomisp_video_pipe *preview_pipe; struct atomisp_video_pipe *video_pipe; capture_pipe = &asd->video_out_capture; preview_pipe = &asd->video_out_preview; video_pipe = &asd->video_out_video_capture; buf_type = atomisp_get_css_buf_type( asd, css_preview_pipe_id, atomisp_subdev_source_pad(&preview_pipe->vdev)); input_stream_id = ATOMISP_INPUT_STREAM_PREVIEW; atomisp_q_video_buffers_to_css(asd, preview_pipe, input_stream_id, buf_type, css_preview_pipe_id); buf_type = atomisp_get_css_buf_type(asd, css_capture_pipe_id, atomisp_subdev_source_pad(&capture_pipe->vdev)); input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; atomisp_q_video_buffers_to_css(asd, capture_pipe, input_stream_id, buf_type, css_capture_pipe_id); buf_type = atomisp_get_css_buf_type(asd, css_video_pipe_id, atomisp_subdev_source_pad(&video_pipe->vdev)); input_stream_id = ATOMISP_INPUT_STREAM_VIDEO; atomisp_q_video_buffers_to_css(asd, video_pipe, input_stream_id, buf_type, css_video_pipe_id); return 0; } /* queue all available buffers to css */ int atomisp_qbuffers_to_css(struct atomisp_sub_device *asd) { enum atomisp_css_buffer_type buf_type; enum atomisp_css_pipe_id css_capture_pipe_id = CSS_PIPE_ID_NUM; enum atomisp_css_pipe_id css_preview_pipe_id = CSS_PIPE_ID_NUM; enum atomisp_css_pipe_id css_video_pipe_id = CSS_PIPE_ID_NUM; enum atomisp_input_stream_id input_stream_id; struct atomisp_video_pipe *capture_pipe = NULL; struct atomisp_video_pipe *vf_pipe = NULL; struct atomisp_video_pipe *preview_pipe = NULL; struct atomisp_video_pipe *video_pipe = NULL; bool raw_mode = atomisp_is_mbuscode_raw( asd->fmt[asd->capture_pad].fmt.code); if (asd->isp->inputs[asd->input_curr].camera_caps-> sensor[asd->sensor_curr].stream_num == 2 && !asd->yuvpp_mode) return atomisp_qbuffers_to_css_for_all_pipes(asd); if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_SCALER) { video_pipe = &asd->video_out_video_capture; css_video_pipe_id = CSS_PIPE_ID_VIDEO; } else if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_LOWLAT) { preview_pipe = &asd->video_out_capture; css_preview_pipe_id = CSS_PIPE_ID_CAPTURE; } else if (asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { if (asd->continuous_mode->val) { capture_pipe = &asd->video_out_capture; vf_pipe = &asd->video_out_vf; css_capture_pipe_id = CSS_PIPE_ID_CAPTURE; } video_pipe = &asd->video_out_video_capture; preview_pipe = &asd->video_out_preview; css_video_pipe_id = CSS_PIPE_ID_VIDEO; css_preview_pipe_id = CSS_PIPE_ID_VIDEO; } else if (asd->continuous_mode->val) { capture_pipe = &asd->video_out_capture; vf_pipe = &asd->video_out_vf; preview_pipe = &asd->video_out_preview; css_preview_pipe_id = CSS_PIPE_ID_PREVIEW; css_capture_pipe_id = CSS_PIPE_ID_CAPTURE; } else if (asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { preview_pipe = &asd->video_out_preview; css_preview_pipe_id = CSS_PIPE_ID_PREVIEW; } else { /* ATOMISP_RUN_MODE_STILL_CAPTURE */ capture_pipe = &asd->video_out_capture; if (!raw_mode) vf_pipe = &asd->video_out_vf; css_capture_pipe_id = CSS_PIPE_ID_CAPTURE; } #ifdef ISP2401_NEW_INPUT_SYSTEM if (asd->copy_mode) { css_capture_pipe_id = CSS_PIPE_ID_COPY; css_preview_pipe_id = CSS_PIPE_ID_COPY; css_video_pipe_id = CSS_PIPE_ID_COPY; } #endif if (asd->yuvpp_mode) { capture_pipe = &asd->video_out_capture; video_pipe = &asd->video_out_video_capture; preview_pipe = &asd->video_out_preview; css_capture_pipe_id = CSS_PIPE_ID_COPY; css_video_pipe_id = CSS_PIPE_ID_YUVPP; css_preview_pipe_id = CSS_PIPE_ID_YUVPP; } if (capture_pipe) { buf_type = atomisp_get_css_buf_type( asd, css_capture_pipe_id, atomisp_subdev_source_pad(&capture_pipe->vdev)); input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; /* * use yuvpp pipe for SOC camera. */ if (ATOMISP_USE_YUVPP(asd)) css_capture_pipe_id = CSS_PIPE_ID_YUVPP; atomisp_q_video_buffers_to_css(asd, capture_pipe, input_stream_id, buf_type, css_capture_pipe_id); } if (vf_pipe) { buf_type = atomisp_get_css_buf_type( asd, css_capture_pipe_id, atomisp_subdev_source_pad(&vf_pipe->vdev)); if (asd->stream_env[ATOMISP_INPUT_STREAM_POSTVIEW].stream) input_stream_id = ATOMISP_INPUT_STREAM_POSTVIEW; else input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; /* * use yuvpp pipe for SOC camera. */ if (ATOMISP_USE_YUVPP(asd)) css_capture_pipe_id = CSS_PIPE_ID_YUVPP; atomisp_q_video_buffers_to_css(asd, vf_pipe, input_stream_id, buf_type, css_capture_pipe_id); } if (preview_pipe) { buf_type = atomisp_get_css_buf_type( asd, css_preview_pipe_id, atomisp_subdev_source_pad(&preview_pipe->vdev)); if (ATOMISP_SOC_CAMERA(asd) && css_preview_pipe_id == CSS_PIPE_ID_YUVPP) input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; /* else for ext isp use case */ else if (css_preview_pipe_id == CSS_PIPE_ID_YUVPP) input_stream_id = ATOMISP_INPUT_STREAM_VIDEO; else if (asd->stream_env[ATOMISP_INPUT_STREAM_PREVIEW].stream) input_stream_id = ATOMISP_INPUT_STREAM_PREVIEW; else input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; /* * use yuvpp pipe for SOC camera. */ if (ATOMISP_USE_YUVPP(asd)) css_preview_pipe_id = CSS_PIPE_ID_YUVPP; atomisp_q_video_buffers_to_css(asd, preview_pipe, input_stream_id, buf_type, css_preview_pipe_id); } if (video_pipe) { buf_type = atomisp_get_css_buf_type( asd, css_video_pipe_id, atomisp_subdev_source_pad(&video_pipe->vdev)); if (asd->stream_env[ATOMISP_INPUT_STREAM_VIDEO].stream) input_stream_id = ATOMISP_INPUT_STREAM_VIDEO; else input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; /* * use yuvpp pipe for SOC camera. */ if (ATOMISP_USE_YUVPP(asd)) css_video_pipe_id = CSS_PIPE_ID_YUVPP; atomisp_q_video_buffers_to_css(asd, video_pipe, input_stream_id, buf_type, css_video_pipe_id); } return 0; } static void atomisp_buf_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) { struct atomisp_video_pipe *pipe = vq->priv_data; /* * when a frame buffer meets following conditions, it should be put into * the waiting list: * 1. It is not a main output frame, and it has a per-frame parameter * to go with it. * 2. It is not a main output frame, and the waiting buffer list is not * empty, to keep the FIFO sequence of frame buffer processing, it * is put to waiting list until previous per-frame parameter buffers * get enqueued. */ if (!atomisp_is_vf_pipe(pipe) && (pipe->frame_request_config_id[vb->i] || !list_empty(&pipe->buffers_waiting_for_param))) list_add_tail(&vb->queue, &pipe->buffers_waiting_for_param); else list_add_tail(&vb->queue, &pipe->activeq); vb->state = VIDEOBUF_QUEUED; } static void atomisp_buf_release(struct videobuf_queue *vq, struct videobuf_buffer *vb) { vb->state = VIDEOBUF_NEEDS_INIT; atomisp_videobuf_free_buf(vb); } static int atomisp_buf_setup_output(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) { struct atomisp_video_pipe *pipe = vq->priv_data; *size = pipe->pix.sizeimage; return 0; } static int atomisp_buf_prepare_output(struct videobuf_queue *vq, struct videobuf_buffer *vb, enum v4l2_field field) { struct atomisp_video_pipe *pipe = vq->priv_data; vb->size = pipe->pix.sizeimage; vb->width = pipe->pix.width; vb->height = pipe->pix.height; vb->field = field; vb->state = VIDEOBUF_PREPARED; return 0; } static void atomisp_buf_queue_output(struct videobuf_queue *vq, struct videobuf_buffer *vb) { struct atomisp_video_pipe *pipe = vq->priv_data; list_add_tail(&vb->queue, &pipe->activeq_out); vb->state = VIDEOBUF_QUEUED; } static void atomisp_buf_release_output(struct videobuf_queue *vq, struct videobuf_buffer *vb) { videobuf_vmalloc_free(vb); vb->state = VIDEOBUF_NEEDS_INIT; } static const struct videobuf_queue_ops videobuf_qops = { .buf_setup = atomisp_buf_setup, .buf_prepare = atomisp_buf_prepare, .buf_queue = atomisp_buf_queue, .buf_release = atomisp_buf_release, }; static const struct videobuf_queue_ops videobuf_qops_output = { .buf_setup = atomisp_buf_setup_output, .buf_prepare = atomisp_buf_prepare_output, .buf_queue = atomisp_buf_queue_output, .buf_release = atomisp_buf_release_output, }; static int atomisp_init_pipe(struct atomisp_video_pipe *pipe) { /* init locks */ spin_lock_init(&pipe->irq_lock); videobuf_queue_vmalloc_init(&pipe->capq, &videobuf_qops, NULL, &pipe->irq_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, sizeof(struct atomisp_buffer), pipe, NULL); /* ext_lock: NULL */ videobuf_queue_vmalloc_init(&pipe->outq, &videobuf_qops_output, NULL, &pipe->irq_lock, V4L2_BUF_TYPE_VIDEO_OUTPUT, V4L2_FIELD_NONE, sizeof(struct atomisp_buffer), pipe, NULL); /* ext_lock: NULL */ INIT_LIST_HEAD(&pipe->activeq); INIT_LIST_HEAD(&pipe->activeq_out); INIT_LIST_HEAD(&pipe->buffers_waiting_for_param); INIT_LIST_HEAD(&pipe->per_frame_params); memset(pipe->frame_request_config_id, 0, VIDEO_MAX_FRAME * sizeof(unsigned int)); memset(pipe->frame_params, 0, VIDEO_MAX_FRAME * sizeof(struct atomisp_css_params_with_list *)); return 0; } static void atomisp_dev_init_struct(struct atomisp_device *isp) { unsigned int i; isp->sw_contex.file_input = 0; isp->need_gfx_throttle = true; isp->isp_fatal_error = false; isp->mipi_frame_size = 0; for (i = 0; i < isp->input_cnt; i++) isp->inputs[i].asd = NULL; /* * For Merrifield, frequency is scalable. * After boot-up, the default frequency is 200MHz. */ isp->sw_contex.running_freq = ISP_FREQ_200MHZ; } static void atomisp_subdev_init_struct(struct atomisp_sub_device *asd) { v4l2_ctrl_s_ctrl(asd->run_mode, ATOMISP_RUN_MODE_STILL_CAPTURE); memset(&asd->params.css_param, 0, sizeof(asd->params.css_param)); asd->params.color_effect = V4L2_COLORFX_NONE; asd->params.bad_pixel_en = 1; asd->params.gdc_cac_en = 0; asd->params.video_dis_en = 0; asd->params.sc_en = 0; asd->params.fpn_en = 0; asd->params.xnr_en = 0; asd->params.false_color = 0; asd->params.online_process = 1; asd->params.yuv_ds_en = 0; /* s3a grid not enabled for any pipe */ asd->params.s3a_enabled_pipe = CSS_PIPE_ID_NUM; asd->params.offline_parm.num_captures = 1; asd->params.offline_parm.skip_frames = 0; asd->params.offline_parm.offset = 0; asd->delayed_init = ATOMISP_DELAYED_INIT_NOT_QUEUED; /* Add for channel */ asd->input_curr = 0; asd->mipi_frame_size = 0; asd->copy_mode = false; asd->yuvpp_mode = false; asd->stream_prepared = false; asd->high_speed_mode = false; asd->sensor_array_res.height = 0; asd->sensor_array_res.width = 0; atomisp_css_init_struct(asd); } /* * file operation functions */ static unsigned int atomisp_subdev_users(struct atomisp_sub_device *asd) { return asd->video_out_preview.users + asd->video_out_vf.users + asd->video_out_capture.users + asd->video_out_video_capture.users + asd->video_acc.users + asd->video_in.users; } unsigned int atomisp_dev_users(struct atomisp_device *isp) { unsigned int i, sum; for (i = 0, sum = 0; i < isp->num_of_streams; i++) sum += atomisp_subdev_users(&isp->asd[i]); return sum; } static int atomisp_open(struct file *file) { struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = NULL; struct atomisp_acc_pipe *acc_pipe = NULL; struct atomisp_sub_device *asd; bool acc_node = false; int ret; dev_dbg(isp->dev, "open device %s\n", vdev->name); rt_mutex_lock(&isp->mutex); acc_node = !strncmp(vdev->name, "ATOMISP ISP ACC", sizeof(vdev->name)); if (acc_node) { acc_pipe = atomisp_to_acc_pipe(vdev); asd = acc_pipe->asd; } else { pipe = atomisp_to_video_pipe(vdev); asd = pipe->asd; } asd->subdev.devnode = vdev; /* Deferred firmware loading case. */ if (isp->css_env.isp_css_fw.bytes == 0) { isp->firmware = atomisp_load_firmware(isp); if (!isp->firmware) { dev_err(isp->dev, "Failed to load ISP firmware.\n"); ret = -ENOENT; goto error; } ret = atomisp_css_load_firmware(isp); if (ret) { dev_err(isp->dev, "Failed to init css.\n"); goto error; } /* No need to keep FW in memory anymore. */ release_firmware(isp->firmware); isp->firmware = NULL; isp->css_env.isp_css_fw.data = NULL; } if (acc_node && acc_pipe->users) { dev_dbg(isp->dev, "acc node already opened\n"); rt_mutex_unlock(&isp->mutex); return -EBUSY; } else if (acc_node) { goto dev_init; } if (!isp->input_cnt) { dev_err(isp->dev, "no camera attached\n"); ret = -EINVAL; goto error; } /* * atomisp does not allow multiple open */ if (pipe->users) { dev_dbg(isp->dev, "video node already opened\n"); rt_mutex_unlock(&isp->mutex); return -EBUSY; } ret = atomisp_init_pipe(pipe); if (ret) goto error; dev_init: if (atomisp_dev_users(isp)) { dev_dbg(isp->dev, "skip init isp in open\n"); goto init_subdev; } /* runtime power management, turn on ISP */ ret = pm_runtime_get_sync(vdev->v4l2_dev->dev); if (ret < 0) { dev_err(isp->dev, "Failed to power on device\n"); goto error; } if (dypool_enable) { ret = hmm_pool_register(dypool_pgnr, HMM_POOL_TYPE_DYNAMIC); if (ret) dev_err(isp->dev, "Failed to register dynamic memory pool.\n"); } /* Init ISP */ if (atomisp_css_init(isp)) { ret = -EINVAL; /* Need to clean up CSS init if it fails. */ goto css_error; } atomisp_dev_init_struct(isp); ret = v4l2_subdev_call(isp->flash, core, s_power, 1); if (ret < 0 && ret != -ENODEV && ret != -ENOIOCTLCMD) { dev_err(isp->dev, "Failed to power-on flash\n"); goto css_error; } init_subdev: if (atomisp_subdev_users(asd)) goto done; atomisp_subdev_init_struct(asd); done: if (acc_node) acc_pipe->users++; else pipe->users++; rt_mutex_unlock(&isp->mutex); return 0; css_error: atomisp_css_uninit(isp); error: hmm_pool_unregister(HMM_POOL_TYPE_DYNAMIC); pm_runtime_put(vdev->v4l2_dev->dev); rt_mutex_unlock(&isp->mutex); return ret; } static int atomisp_release(struct file *file) { struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe; struct atomisp_acc_pipe *acc_pipe; struct atomisp_sub_device *asd; bool acc_node; struct v4l2_requestbuffers req; struct v4l2_subdev_fh fh; struct v4l2_rect clear_compose = {0}; int ret = 0; v4l2_fh_init(&fh.vfh, vdev); req.count = 0; if (isp == NULL) return -EBADF; mutex_lock(&isp->streamoff_mutex); rt_mutex_lock(&isp->mutex); dev_dbg(isp->dev, "release device %s\n", vdev->name); acc_node = !strncmp(vdev->name, "ATOMISP ISP ACC", sizeof(vdev->name)); if (acc_node) { acc_pipe = atomisp_to_acc_pipe(vdev); asd = acc_pipe->asd; } else { pipe = atomisp_to_video_pipe(vdev); asd = pipe->asd; } asd->subdev.devnode = vdev; if (acc_node) { acc_pipe->users--; goto subdev_uninit; } pipe->users--; if (pipe->capq.streaming) dev_warn(isp->dev, "%s: ISP still streaming while closing!", __func__); if (pipe->capq.streaming && __atomisp_streamoff(file, NULL, V4L2_BUF_TYPE_VIDEO_CAPTURE)) { dev_err(isp->dev, "atomisp_streamoff failed on release, driver bug"); goto done; } if (pipe->users) goto done; if (__atomisp_reqbufs(file, NULL, &req)) { dev_err(isp->dev, "atomisp_reqbufs failed on release, driver bug"); goto done; } if (pipe->outq.bufs[0]) { mutex_lock(&pipe->outq.vb_lock); videobuf_queue_cancel(&pipe->outq); mutex_unlock(&pipe->outq.vb_lock); } /* * A little trick here: * file injection input resolution is recorded in the sink pad, * therefore can not be cleared when releaseing one device node. * The sink pad setting can only be cleared when all device nodes * get released. */ if (!isp->sw_contex.file_input && asd->fmt_auto->val) { struct v4l2_mbus_framefmt isp_sink_fmt = { 0 }; atomisp_subdev_set_ffmt(&asd->subdev, fh.pad, V4L2_SUBDEV_FORMAT_ACTIVE, ATOMISP_SUBDEV_PAD_SINK, &isp_sink_fmt); } subdev_uninit: if (atomisp_subdev_users(asd)) goto done; /* clear the sink pad for file input */ if (isp->sw_contex.file_input && asd->fmt_auto->val) { struct v4l2_mbus_framefmt isp_sink_fmt = { 0 }; atomisp_subdev_set_ffmt(&asd->subdev, fh.pad, V4L2_SUBDEV_FORMAT_ACTIVE, ATOMISP_SUBDEV_PAD_SINK, &isp_sink_fmt); } atomisp_css_free_stat_buffers(asd); atomisp_free_internal_buffers(asd); ret = v4l2_subdev_call(isp->inputs[asd->input_curr].camera, core, s_power, 0); if (ret) dev_warn(isp->dev, "Failed to power-off sensor\n"); /* clear the asd field to show this camera is not used */ isp->inputs[asd->input_curr].asd = NULL; asd->streaming = ATOMISP_DEVICE_STREAMING_DISABLED; if (atomisp_dev_users(isp)) goto done; atomisp_acc_release(asd); atomisp_destroy_pipes_stream_force(asd); atomisp_css_uninit(isp); if (defer_fw_load) { atomisp_css_unload_firmware(isp); isp->css_env.isp_css_fw.data = NULL; isp->css_env.isp_css_fw.bytes = 0; } hmm_pool_unregister(HMM_POOL_TYPE_DYNAMIC); ret = v4l2_subdev_call(isp->flash, core, s_power, 0); if (ret < 0 && ret != -ENODEV && ret != -ENOIOCTLCMD) dev_warn(isp->dev, "Failed to power-off flash\n"); if (pm_runtime_put_sync(vdev->v4l2_dev->dev) < 0) dev_err(isp->dev, "Failed to power off device\n"); done: if (!acc_node) { atomisp_subdev_set_selection(&asd->subdev, fh.pad, V4L2_SUBDEV_FORMAT_ACTIVE, atomisp_subdev_source_pad(vdev), V4L2_SEL_TGT_COMPOSE, 0, &clear_compose); } rt_mutex_unlock(&isp->mutex); mutex_unlock(&isp->streamoff_mutex); return 0; } /* * Memory help functions for image frame and private parameters */ static int do_isp_mm_remap(struct atomisp_device *isp, struct vm_area_struct *vma, ia_css_ptr isp_virt, u32 host_virt, u32 pgnr) { u32 pfn; while (pgnr) { pfn = hmm_virt_to_phys(isp_virt) >> PAGE_SHIFT; if (remap_pfn_range(vma, host_virt, pfn, PAGE_SIZE, PAGE_SHARED)) { dev_err(isp->dev, "remap_pfn_range err.\n"); return -EAGAIN; } isp_virt += PAGE_SIZE; host_virt += PAGE_SIZE; pgnr--; } return 0; } static int frame_mmap(struct atomisp_device *isp, const struct atomisp_css_frame *frame, struct vm_area_struct *vma) { ia_css_ptr isp_virt; u32 host_virt; u32 pgnr; if (!frame) { dev_err(isp->dev, "%s: NULL frame pointer.\n", __func__); return -EINVAL; } host_virt = vma->vm_start; isp_virt = frame->data; atomisp_get_frame_pgnr(isp, frame, &pgnr); if (do_isp_mm_remap(isp, vma, isp_virt, host_virt, pgnr)) return -EAGAIN; return 0; } int atomisp_videobuf_mmap_mapper(struct videobuf_queue *q, struct vm_area_struct *vma) { u32 offset = vma->vm_pgoff << PAGE_SHIFT; int ret = -EINVAL, i; struct atomisp_device *isp = ((struct atomisp_video_pipe *)(q->priv_data))->isp; struct videobuf_vmalloc_memory *vm_mem; struct videobuf_mapping *map; MAGIC_CHECK(q->int_ops->magic, MAGIC_QTYPE_OPS); if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_SHARED)) { dev_err(isp->dev, "map appl bug: PROT_WRITE and MAP_SHARED are required\n"); return -EINVAL; } mutex_lock(&q->vb_lock); for (i = 0; i < VIDEO_MAX_FRAME; i++) { struct videobuf_buffer *buf = q->bufs[i]; if (buf == NULL) continue; map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); if (!map) { mutex_unlock(&q->vb_lock); return -ENOMEM; } buf->map = map; map->q = q; buf->baddr = vma->vm_start; if (buf && buf->memory == V4L2_MEMORY_MMAP && buf->boff == offset) { vm_mem = buf->priv; ret = frame_mmap(isp, vm_mem->vaddr, vma); vma->vm_flags |= VM_IO|VM_DONTEXPAND|VM_DONTDUMP; break; } } mutex_unlock(&q->vb_lock); return ret; } /* The input frame contains left and right padding that need to be removed. * There is always ISP_LEFT_PAD padding on the left side. * There is also padding on the right (padded_width - width). */ static int remove_pad_from_frame(struct atomisp_device *isp, struct atomisp_css_frame *in_frame, __u32 width, __u32 height) { unsigned int i; unsigned short *buffer; int ret = 0; ia_css_ptr load = in_frame->data; ia_css_ptr store = load; buffer = kmalloc(width*sizeof(load), GFP_KERNEL); if (!buffer) return -ENOMEM; load += ISP_LEFT_PAD; for (i = 0; i < height; i++) { ret = hmm_load(load, buffer, width*sizeof(load)); if (ret < 0) goto remove_pad_error; ret = hmm_store(store, buffer, width*sizeof(store)); if (ret < 0) goto remove_pad_error; load += in_frame->info.padded_width; store += width; } remove_pad_error: kfree(buffer); return ret; } static int atomisp_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); struct atomisp_sub_device *asd = pipe->asd; struct atomisp_css_frame *raw_virt_addr; u32 start = vma->vm_start; u32 end = vma->vm_end; u32 size = end - start; u32 origin_size, new_size; int ret; if (!(vma->vm_flags & (VM_WRITE | VM_READ))) return -EACCES; rt_mutex_lock(&isp->mutex); if (!(vma->vm_flags & VM_SHARED)) { /* Map private buffer. * Set VM_SHARED to the flags since we need * to map the buffer page by page. * Without VM_SHARED, remap_pfn_range() treats * this kind of mapping as invalid. */ vma->vm_flags |= VM_SHARED; ret = hmm_mmap(vma, vma->vm_pgoff << PAGE_SHIFT); rt_mutex_unlock(&isp->mutex); return ret; } /* mmap for ISP offline raw data */ if (atomisp_subdev_source_pad(vdev) == ATOMISP_SUBDEV_PAD_SOURCE_CAPTURE && vma->vm_pgoff == (ISP_PARAM_MMAP_OFFSET >> PAGE_SHIFT)) { new_size = pipe->pix.width * pipe->pix.height * 2; if (asd->params.online_process != 0) { ret = -EINVAL; goto error; } raw_virt_addr = asd->raw_output_frame; if (raw_virt_addr == NULL) { dev_err(isp->dev, "Failed to request RAW frame\n"); ret = -EINVAL; goto error; } ret = remove_pad_from_frame(isp, raw_virt_addr, pipe->pix.width, pipe->pix.height); if (ret < 0) { dev_err(isp->dev, "remove pad failed.\n"); goto error; } origin_size = raw_virt_addr->data_bytes; raw_virt_addr->data_bytes = new_size; if (size != PAGE_ALIGN(new_size)) { dev_err(isp->dev, "incorrect size for mmap ISP Raw Frame\n"); ret = -EINVAL; goto error; } if (frame_mmap(isp, raw_virt_addr, vma)) { dev_err(isp->dev, "frame_mmap failed.\n"); raw_virt_addr->data_bytes = origin_size; ret = -EAGAIN; goto error; } raw_virt_addr->data_bytes = origin_size; vma->vm_flags |= VM_IO|VM_DONTEXPAND|VM_DONTDUMP; rt_mutex_unlock(&isp->mutex); return 0; } /* * mmap for normal frames */ if (size != pipe->pix.sizeimage) { dev_err(isp->dev, "incorrect size for mmap ISP frames\n"); ret = -EINVAL; goto error; } rt_mutex_unlock(&isp->mutex); return atomisp_videobuf_mmap_mapper(&pipe->capq, vma); error: rt_mutex_unlock(&isp->mutex); return ret; } static int atomisp_file_mmap(struct file *file, struct vm_area_struct *vma) { struct video_device *vdev = video_devdata(file); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); return videobuf_mmap_mapper(&pipe->outq, vma); } static __poll_t atomisp_poll(struct file *file, struct poll_table_struct *pt) { struct video_device *vdev = video_devdata(file); struct atomisp_device *isp = video_get_drvdata(vdev); struct atomisp_video_pipe *pipe = atomisp_to_video_pipe(vdev); rt_mutex_lock(&isp->mutex); if (pipe->capq.streaming != 1) { rt_mutex_unlock(&isp->mutex); return EPOLLERR; } rt_mutex_unlock(&isp->mutex); return videobuf_poll_stream(file, &pipe->capq, pt); } const struct v4l2_file_operations atomisp_fops = { .owner = THIS_MODULE, .open = atomisp_open, .release = atomisp_release, .mmap = atomisp_mmap, .unlocked_ioctl = video_ioctl2, #ifdef CONFIG_COMPAT .compat_ioctl32 = atomisp_compat_ioctl32, #endif .poll = atomisp_poll, }; const struct v4l2_file_operations atomisp_file_fops = { .owner = THIS_MODULE, .open = atomisp_open, .release = atomisp_release, .mmap = atomisp_file_mmap, .unlocked_ioctl = video_ioctl2, #ifdef CONFIG_COMPAT .compat_ioctl32 = atomisp_compat_ioctl32, #endif .poll = atomisp_poll, };