diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/staging/media/atomisp/pci/atomisp_fops.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/staging/media/atomisp/pci/atomisp_fops.c')
-rw-r--r-- | drivers/staging/media/atomisp/pci/atomisp_fops.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/drivers/staging/media/atomisp/pci/atomisp_fops.c b/drivers/staging/media/atomisp/pci/atomisp_fops.c new file mode 100644 index 0000000000..54466d2f32 --- /dev/null +++ b/drivers/staging/media/atomisp/pci/atomisp_fops.c @@ -0,0 +1,632 @@ +// 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/videobuf2-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 "ia_css_frame.h" +#include "type_support.h" +#include "device_access/device_access.h" + +/* + * Videobuf2 ops + */ +static int atomisp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct atomisp_video_pipe *pipe = container_of(vq, struct atomisp_video_pipe, vb_queue); + int ret; + + mutex_lock(&pipe->asd->isp->mutex); /* for get_css_frame_info() / set_fmt() */ + + /* + * When VIDIOC_S_FMT has not been called before VIDIOC_REQBUFS, then + * this will fail. Call atomisp_set_fmt() ourselves and try again. + */ + ret = atomisp_get_css_frame_info(pipe->asd, &pipe->frame_info); + if (ret) { + struct v4l2_format f = { + .fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420, + .fmt.pix.width = 10000, + .fmt.pix.height = 10000, + }; + + ret = atomisp_set_fmt(&pipe->vdev, &f); + if (ret) + goto out; + + ret = atomisp_get_css_frame_info(pipe->asd, &pipe->frame_info); + if (ret) + goto out; + } + + atomisp_alloc_css_stat_bufs(pipe->asd, ATOMISP_INPUT_STREAM_GENERAL); + + *nplanes = 1; + sizes[0] = PAGE_ALIGN(pipe->pix.sizeimage); + +out: + mutex_unlock(&pipe->asd->isp->mutex); + return ret; +} + +static int atomisp_buf_init(struct vb2_buffer *vb) +{ + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + int ret; + + ret = ia_css_frame_init_from_info(frame, &pipe->frame_info); + if (ret) + return ret; + + if (frame->data_bytes > vb2_plane_size(vb, 0)) { + dev_err(pipe->asd->isp->dev, "Internal error frame.data_bytes(%u) > vb.length(%lu)\n", + frame->data_bytes, vb2_plane_size(vb, 0)); + return -EIO; + } + + frame->data = hmm_create_from_vmalloc_buf(vb2_plane_size(vb, 0), + vb2_plane_vaddr(vb, 0)); + if (frame->data == mmgr_NULL) + return -ENOMEM; + + 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_MAIN_METADATA; + 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_dbg(asd->isp->dev, "drop one s3a stat with exp_id %d\n", 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; +} + +static 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 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 space, err = 0; + + lockdep_assert_held(&asd->isp->mutex); + + if (WARN_ON(css_pipe_id >= IA_CSS_PIPE_ID_NUM)) + return -EINVAL; + + if (pipe->stopping) + return -EINVAL; + + space = ATOMISP_CSS_Q_DEPTH - atomisp_buffers_in_css(pipe); + while (space--) { + struct ia_css_frame *frame; + + spin_lock_irqsave(&pipe->irq_lock, irqflags); + frame = list_first_entry_or_null(&pipe->activeq, struct ia_css_frame, queue); + if (frame) + list_move_tail(&frame->queue, &pipe->buffers_in_css); + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + + if (!frame) + return -EINVAL; + + /* + * If there is a per_frame setting to apply on the buffer, + * do it before buffer en-queueing. + */ + param = pipe->frame_params[frame->vb.vb2_buf.index]; + 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, frame); + 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; + } + pipe->frame_params[frame->vb.vb2_buf.index] = NULL; + } + /* Enqueue buffer */ + err = atomisp_q_video_buffer_to_css(asd, frame, stream_id, + css_buf_type, css_pipe_id); + if (err) { + spin_lock_irqsave(&pipe->irq_lock, irqflags); + list_move_tail(&frame->queue, &pipe->activeq); + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + dev_err(asd->isp->dev, "%s, css q fails: %d\n", + __func__, err); + return -EINVAL; + } + + /* 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; +} + +/* queue all available buffers to css */ +int atomisp_qbuffers_to_css(struct atomisp_sub_device *asd) +{ + enum ia_css_pipe_id pipe_id; + + if (asd->copy_mode) { + pipe_id = IA_CSS_PIPE_ID_COPY; + } else if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_SCALER) { + pipe_id = IA_CSS_PIPE_ID_VIDEO; + } else if (asd->vfpp->val == ATOMISP_VFPP_DISABLE_LOWLAT) { + pipe_id = IA_CSS_PIPE_ID_CAPTURE; + } else if (asd->run_mode->val == ATOMISP_RUN_MODE_VIDEO) { + pipe_id = IA_CSS_PIPE_ID_VIDEO; + } else if (asd->run_mode->val == ATOMISP_RUN_MODE_PREVIEW) { + pipe_id = IA_CSS_PIPE_ID_PREVIEW; + } else { + /* ATOMISP_RUN_MODE_STILL_CAPTURE */ + pipe_id = IA_CSS_PIPE_ID_CAPTURE; + } + + atomisp_q_video_buffers_to_css(asd, &asd->video_out, + ATOMISP_INPUT_STREAM_GENERAL, + IA_CSS_BUFFER_TYPE_OUTPUT_FRAME, pipe_id); + return 0; +} + +static void atomisp_buf_queue(struct vb2_buffer *vb) +{ + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + struct atomisp_sub_device *asd = pipe->asd; + unsigned long irqflags; + int ret; + + mutex_lock(&asd->isp->mutex); + + ret = atomisp_pipe_check(pipe, false); + if (ret || pipe->stopping) { + spin_lock_irqsave(&pipe->irq_lock, irqflags); + atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + goto out_unlock; + } + + /* FIXME this ugliness comes from the original atomisp buffer handling */ + if (!(vb->skip_cache_sync_on_finish && vb->skip_cache_sync_on_prepare)) + wbinvd(); + + pipe->frame_params[vb->index] = NULL; + + spin_lock_irqsave(&pipe->irq_lock, irqflags); + /* + * 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 (pipe->frame_request_config_id[vb->index] || + !list_empty(&pipe->buffers_waiting_for_param)) + list_add_tail(&frame->queue, &pipe->buffers_waiting_for_param); + else + list_add_tail(&frame->queue, &pipe->activeq); + + spin_unlock_irqrestore(&pipe->irq_lock, irqflags); + + /* TODO: do this better, not best way to queue to css */ + if (asd->streaming) { + if (!list_empty(&pipe->buffers_waiting_for_param)) + atomisp_handle_parameter_and_buffer(pipe); + else + atomisp_qbuffers_to_css(asd); + } + +out_unlock: + mutex_unlock(&asd->isp->mutex); +} + +static void atomisp_buf_cleanup(struct vb2_buffer *vb) +{ + struct atomisp_video_pipe *pipe = vb_to_pipe(vb); + struct ia_css_frame *frame = vb_to_frame(vb); + int index = frame->vb.vb2_buf.index; + + pipe->frame_request_config_id[index] = 0; + pipe->frame_params[index] = NULL; + + hmm_free(frame->data); +} + +const struct vb2_ops atomisp_vb2_ops = { + .queue_setup = atomisp_queue_setup, + .buf_init = atomisp_buf_init, + .buf_cleanup = atomisp_buf_cleanup, + .buf_queue = atomisp_buf_queue, + .start_streaming = atomisp_start_streaming, + .stop_streaming = atomisp_stop_streaming, +}; + +static void atomisp_dev_init_struct(struct atomisp_device *isp) +{ + unsigned int i; + + isp->isp_fatal_error = false; + + 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->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.yuv_ds_en = 0; + /* s3a grid not enabled for any pipe */ + asd->params.s3a_enabled_pipe = IA_CSS_PIPE_ID_NUM; + + asd->copy_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 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 = atomisp_to_video_pipe(vdev); + struct atomisp_sub_device *asd = pipe->asd; + int ret; + + dev_dbg(isp->dev, "open device %s\n", vdev->name); + + ret = v4l2_fh_open(file); + if (ret) + return ret; + + mutex_lock(&isp->mutex); + + 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"); + mutex_unlock(&isp->mutex); + return -EBUSY; + } + + /* runtime power management, turn on ISP */ + ret = pm_runtime_resume_and_get(vdev->v4l2_dev->dev); + if (ret < 0) { + dev_err(isp->dev, "Failed to power on device\n"); + goto 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; + } + + atomisp_subdev_init_struct(asd); + /* Ensure that a mode is set */ + v4l2_ctrl_s_ctrl(asd->run_mode, ATOMISP_RUN_MODE_PREVIEW); + + pipe->users++; + mutex_unlock(&isp->mutex); + return 0; + +css_error: + pm_runtime_put(vdev->v4l2_dev->dev); +error: + mutex_unlock(&isp->mutex); + v4l2_fh_release(file); + 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 = atomisp_to_video_pipe(vdev); + struct atomisp_sub_device *asd = pipe->asd; + struct v4l2_subdev_fh fh; + struct v4l2_rect clear_compose = {0}; + int ret; + + v4l2_fh_init(&fh.vfh, vdev); + + dev_dbg(isp->dev, "release device %s\n", vdev->name); + + /* Note file must not be used after this! */ + vb2_fop_release(file); + + mutex_lock(&isp->mutex); + + pipe->users--; + + /* + * 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. + */ + { + struct v4l2_mbus_framefmt isp_sink_fmt = { 0 }; + + atomisp_subdev_set_ffmt(&asd->subdev, fh.state, + V4L2_SUBDEV_FORMAT_ACTIVE, + ATOMISP_SUBDEV_PAD_SINK, &isp_sink_fmt); + } + + atomisp_css_free_stat_buffers(asd); + atomisp_free_internal_buffers(asd); + + if (isp->inputs[asd->input_curr].asd == asd) { + ret = v4l2_subdev_call(isp->inputs[asd->input_curr].camera, + core, s_power, 0); + if (ret && ret != -ENOIOCTLCMD) + 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; + } + + atomisp_destroy_pipes_stream(asd); + + 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"); + + atomisp_subdev_set_selection(&asd->subdev, fh.state, + V4L2_SUBDEV_FORMAT_ACTIVE, + ATOMISP_SUBDEV_PAD_SOURCE, + V4L2_SEL_TGT_COMPOSE, 0, + &clear_compose); + mutex_unlock(&isp->mutex); + return 0; +} + +const struct v4l2_file_operations atomisp_fops = { + .owner = THIS_MODULE, + .open = atomisp_open, + .release = atomisp_release, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, +#ifdef CONFIG_COMPAT + /* + * this was removed because of bugs, the interface + * needs to be made safe for compat tasks instead. + .compat_ioctl32 = atomisp_compat_ioctl32, + */ +#endif +}; |