| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * 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 <linux/module.h> |
| #include <linux/pm_runtime.h> |
| |
| #include <media/v4l2-ioctl.h> |
| #include <media/videobuf-vmalloc.h> |
| |
| #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 "type_support.h" |
| #include "device_access/device_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 ia_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 ia_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 ia_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 ia_css_buffer_type css_buf_type, |
| enum ia_css_pipe_id css_pipe_id) |
| { |
| struct videobuf_vmalloc_memory *vm_mem; |
| struct atomisp_css_params_with_list *param; |
| struct ia_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) |
| asd->params.config.dz_config = ¶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 ia_css_dvs_6axis_config *) |
| 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 include |
| * 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 == IA_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 == IA_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 == IA_CSS_PIPE_ID_VIDEO && |
| css_buf_type == IA_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 ia_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 IA_CSS_BUFFER_TYPE_OUTPUT_FRAME; |
| else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW) |
| return IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME; |
| else |
| return IA_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 IA_CSS_BUFFER_TYPE_OUTPUT_FRAME; |
| else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_PREVIEW) |
| return IA_CSS_BUFFER_TYPE_SEC_VF_OUTPUT_FRAME; |
| else if (source_pad == ATOMISP_SUBDEV_PAD_SOURCE_VIDEO) |
| return IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME; |
| else |
| return IA_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 IA_CSS_BUFFER_TYPE_OUTPUT_FRAME; |
| else |
| return IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; |
| } |
| |
| if (pipe_id == IA_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 IA_CSS_BUFFER_TYPE_OUTPUT_FRAME; |
| else |
| return IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME; |
| } |
| |
| static int atomisp_qbuffers_to_css_for_all_pipes(struct atomisp_sub_device *asd) |
| { |
| enum ia_css_buffer_type buf_type; |
| enum ia_css_pipe_id css_capture_pipe_id = IA_CSS_PIPE_ID_COPY; |
| enum ia_css_pipe_id css_preview_pipe_id = IA_CSS_PIPE_ID_COPY; |
| enum ia_css_pipe_id css_video_pipe_id = IA_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 ia_css_buffer_type buf_type; |
| enum ia_css_pipe_id css_capture_pipe_id = IA_CSS_PIPE_ID_NUM; |
| enum ia_css_pipe_id css_preview_pipe_id = IA_CSS_PIPE_ID_NUM; |
| enum ia_css_pipe_id css_video_pipe_id = IA_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 = IA_CSS_PIPE_ID_VIDEO; |
| } else if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_LOWLAT) { |
| preview_pipe = &asd->video_out_capture; |
| css_preview_pipe_id = IA_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 = IA_CSS_PIPE_ID_CAPTURE; |
| } |
| video_pipe = &asd->video_out_video_capture; |
| preview_pipe = &asd->video_out_preview; |
| css_video_pipe_id = IA_CSS_PIPE_ID_VIDEO; |
| css_preview_pipe_id = IA_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 = IA_CSS_PIPE_ID_PREVIEW; |
| css_capture_pipe_id = IA_CSS_PIPE_ID_CAPTURE; |
| } else if (asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { |
| preview_pipe = &asd->video_out_preview; |
| css_preview_pipe_id = IA_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 = IA_CSS_PIPE_ID_CAPTURE; |
| } |
| |
| #ifdef ISP2401_NEW_INPUT_SYSTEM |
| if (asd->copy_mode) { |
| css_capture_pipe_id = IA_CSS_PIPE_ID_COPY; |
| css_preview_pipe_id = IA_CSS_PIPE_ID_COPY; |
| css_video_pipe_id = IA_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 = IA_CSS_PIPE_ID_COPY; |
| css_video_pipe_id = IA_CSS_PIPE_ID_YUVPP; |
| css_preview_pipe_id = IA_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 = IA_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 = IA_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 == IA_CSS_PIPE_ID_YUVPP) |
| input_stream_id = ATOMISP_INPUT_STREAM_GENERAL; |
| /* else for ext isp use case */ |
| else if (css_preview_pipe_id == IA_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 = IA_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 = IA_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 = false; |
| 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 = true; |
| asd->params.gdc_cac_en = false; |
| asd->params.video_dis_en = false; |
| asd->params.sc_en = false; |
| asd->params.fpn_en = false; |
| asd->params.xnr_en = false; |
| 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 = IA_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 = !strcmp(vdev->name, "ATOMISP ISP ACC"); |
| 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) { |
| dev_err(isp->dev, "Deferred firmware load.\n"); |
| 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) |
| return -EBADF; |
| |
| mutex_lock(&isp->streamoff_mutex); |
| rt_mutex_lock(&isp->mutex); |
| |
| dev_dbg(isp->dev, "release device %s\n", vdev->name); |
| acc_node = !strcmp(vdev->name, "ATOMISP ISP ACC"); |
| 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) { |
| ia_css_unload_firmware(); |
| 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 ia_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) |
| 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 ia_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_array(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 ia_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) { |
| 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 |
| /* |
| * There are problems with this code. Disable this for now. |
| .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 |
| /* |
| * There are problems with this code. Disable this for now. |
| .compat_ioctl32 = atomisp_compat_ioctl32, |
| */ |
| #endif |
| .poll = atomisp_poll, |
| }; |