diff options
Diffstat (limited to 'drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c')
-rw-r--r-- | drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c new file mode 100644 index 000000000000..8039e311cb1c --- /dev/null +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-vmalloc.h> +#include <media/videobuf2-v4l2.h> + +#include "sun6i_isp.h" +#include "sun6i_isp_params.h" +#include "sun6i_isp_reg.h" +#include "uapi/sun6i-isp-config.h" + +/* Params */ + +static const struct sun6i_isp_params_config sun6i_isp_params_config_default = { + .modules_used = SUN6I_ISP_MODULE_BAYER, + + .bayer = { + .offset_r = 32, + .offset_gr = 32, + .offset_gb = 32, + .offset_b = 32, + + .gain_r = 256, + .gain_gr = 256, + .gain_gb = 256, + .gain_b = 256, + + }, + + .bdnf = { + .in_dis_min = 8, + .in_dis_max = 16, + + .coefficients_g = { 15, 4, 1 }, + .coefficients_rb = { 15, 4 }, + }, +}; + +static void sun6i_isp_params_configure_ob(struct sun6i_isp_device *isp_dev) +{ + unsigned int width, height; + + sun6i_isp_proc_dimensions(isp_dev, &width, &height); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SIZE_REG, + SUN6I_ISP_OB_SIZE_WIDTH(width) | + SUN6I_ISP_OB_SIZE_HEIGHT(height)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_VALID_REG, + SUN6I_ISP_OB_VALID_WIDTH(width) | + SUN6I_ISP_OB_VALID_HEIGHT(height)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SRC0_VALID_START_REG, + SUN6I_ISP_OB_SRC0_VALID_START_HORZ(0) | + SUN6I_ISP_OB_SRC0_VALID_START_VERT(0)); +} + +static void sun6i_isp_params_configure_ae(struct sun6i_isp_device *isp_dev) +{ + /* These are default values that need to be set to get an output. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_AE_CFG_REG, + SUN6I_ISP_AE_CFG_LOW_BRI_TH(0xff) | + SUN6I_ISP_AE_CFG_HORZ_NUM(8) | + SUN6I_ISP_AE_CFG_HIGH_BRI_TH(0xf00) | + SUN6I_ISP_AE_CFG_VERT_NUM(8)); +} + +static void +sun6i_isp_params_configure_bayer(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + const struct sun6i_isp_params_config_bayer *bayer = &config->bayer; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET0_REG, + SUN6I_ISP_BAYER_OFFSET0_R(bayer->offset_r) | + SUN6I_ISP_BAYER_OFFSET0_GR(bayer->offset_gr)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET1_REG, + SUN6I_ISP_BAYER_OFFSET1_GB(bayer->offset_gb) | + SUN6I_ISP_BAYER_OFFSET1_B(bayer->offset_b)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN0_REG, + SUN6I_ISP_BAYER_GAIN0_R(bayer->gain_r) | + SUN6I_ISP_BAYER_GAIN0_GR(bayer->gain_gr)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN1_REG, + SUN6I_ISP_BAYER_GAIN1_GB(bayer->gain_gb) | + SUN6I_ISP_BAYER_GAIN1_B(bayer->gain_b)); +} + +static void sun6i_isp_params_configure_wb(struct sun6i_isp_device *isp_dev) +{ + /* These are default values that need to be set to get an output. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN0_REG, + SUN6I_ISP_WB_GAIN0_R(256) | + SUN6I_ISP_WB_GAIN0_GR(256)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN1_REG, + SUN6I_ISP_WB_GAIN1_GB(256) | + SUN6I_ISP_WB_GAIN1_B(256)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_CFG_REG, + SUN6I_ISP_WB_CFG_CLIP(0xfff)); +} + +static void sun6i_isp_params_configure_base(struct sun6i_isp_device *isp_dev) +{ + sun6i_isp_params_configure_ae(isp_dev); + sun6i_isp_params_configure_ob(isp_dev); + sun6i_isp_params_configure_wb(isp_dev); +} + +static void +sun6i_isp_params_configure_bdnf(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + const struct sun6i_isp_params_config_bdnf *bdnf = &config->bdnf; + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_CFG_REG, + SUN6I_ISP_BDNF_CFG_IN_DIS_MIN(bdnf->in_dis_min) | + SUN6I_ISP_BDNF_CFG_IN_DIS_MAX(bdnf->in_dis_max)); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_RB_REG, + SUN6I_ISP_BDNF_COEF_RB(0, bdnf->coefficients_rb[0]) | + SUN6I_ISP_BDNF_COEF_RB(1, bdnf->coefficients_rb[1]) | + SUN6I_ISP_BDNF_COEF_RB(2, bdnf->coefficients_rb[2]) | + SUN6I_ISP_BDNF_COEF_RB(3, bdnf->coefficients_rb[3]) | + SUN6I_ISP_BDNF_COEF_RB(4, bdnf->coefficients_rb[4])); + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_G_REG, + SUN6I_ISP_BDNF_COEF_G(0, bdnf->coefficients_g[0]) | + SUN6I_ISP_BDNF_COEF_G(1, bdnf->coefficients_g[1]) | + SUN6I_ISP_BDNF_COEF_G(2, bdnf->coefficients_g[2]) | + SUN6I_ISP_BDNF_COEF_G(3, bdnf->coefficients_g[3]) | + SUN6I_ISP_BDNF_COEF_G(4, bdnf->coefficients_g[4]) | + SUN6I_ISP_BDNF_COEF_G(5, bdnf->coefficients_g[5]) | + SUN6I_ISP_BDNF_COEF_G(6, bdnf->coefficients_g[6])); +} + +static void +sun6i_isp_params_configure_modules(struct sun6i_isp_device *isp_dev, + const struct sun6i_isp_params_config *config) +{ + u32 value; + + if (config->modules_used & SUN6I_ISP_MODULE_BDNF) + sun6i_isp_params_configure_bdnf(isp_dev, config); + + if (config->modules_used & SUN6I_ISP_MODULE_BAYER) + sun6i_isp_params_configure_bayer(isp_dev, config); + + value = sun6i_isp_load_read(isp_dev, SUN6I_ISP_MODULE_EN_REG); + /* Clear all modules but keep input configuration. */ + value &= SUN6I_ISP_MODULE_EN_SRC0 | SUN6I_ISP_MODULE_EN_SRC1; + + if (config->modules_used & SUN6I_ISP_MODULE_BDNF) + value |= SUN6I_ISP_MODULE_EN_BDNF; + + /* Bayer stage is always enabled. */ + + sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODULE_EN_REG, value); +} + +void sun6i_isp_params_configure(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + sun6i_isp_params_configure_base(isp_dev); + + /* Default config is only applied at the very first stream start. */ + if (state->configured) + goto complete; + + sun6i_isp_params_configure_modules(isp_dev, + &sun6i_isp_params_config_default); + + state->configured = true; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +/* State */ + +static void sun6i_isp_params_state_cleanup(struct sun6i_isp_device *isp_dev, + bool error) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (state->pending) { + vb2_buffer = &state->pending->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + list_for_each_entry(isp_buffer, &state->queue, list) { + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_params_state_update(struct sun6i_isp_device *isp_dev, + bool *update) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + const struct sun6i_isp_params_config *config; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + isp_buffer = list_first_entry(&state->queue, struct sun6i_isp_buffer, + list); + + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + config = vb2_plane_vaddr(vb2_buffer, 0); + + sun6i_isp_params_configure_modules(isp_dev, config); + + list_del(&isp_buffer->list); + + state->pending = isp_buffer; + + if (update) + *update = true; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_isp_params_state_complete(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct sun6i_isp_buffer *isp_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + isp_buffer = state->pending; + vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + + /* Parameters will be applied starting from the next frame. */ + isp_buffer->v4l2_buffer.sequence = isp_dev->capture.state.sequence + 1; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->pending = NULL; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +/* Queue */ + +static int sun6i_isp_params_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + unsigned int size = isp_dev->params.format.fmt.meta.buffersize; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_isp_params_buffer_prepare(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + unsigned int size = isp_dev->params.format.fmt.meta.buffersize; + + if (vb2_plane_size(vb2_buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb2_buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb2_buffer, 0, size); + + return 0; +} + +static void sun6i_isp_params_buffer_queue(struct vb2_buffer *vb2_buffer) +{ + struct sun6i_isp_device *isp_dev = + vb2_get_drv_priv(vb2_buffer->vb2_queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(vb2_buffer); + struct sun6i_isp_buffer *isp_buffer = + container_of(v4l2_buffer, struct sun6i_isp_buffer, v4l2_buffer); + bool capture_streaming = isp_dev->capture.state.streaming; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&isp_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); + + if (state->streaming && capture_streaming) + sun6i_isp_state_update(isp_dev, false); +} + +static int sun6i_isp_params_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + bool capture_streaming = isp_dev->capture.state.streaming; + + state->streaming = true; + + /* + * Update the state as soon as possible if capture is streaming, + * otherwise it will be applied when capture starts streaming. + */ + + if (capture_streaming) + sun6i_isp_state_update(isp_dev, false); + + return 0; +} + +static void sun6i_isp_params_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue); + struct sun6i_isp_params_state *state = &isp_dev->params.state; + + state->streaming = false; + sun6i_isp_params_state_cleanup(isp_dev, true); +} + +static const struct vb2_ops sun6i_isp_params_queue_ops = { + .queue_setup = sun6i_isp_params_queue_setup, + .buf_prepare = sun6i_isp_params_buffer_prepare, + .buf_queue = sun6i_isp_params_buffer_queue, + .start_streaming = sun6i_isp_params_start_streaming, + .stop_streaming = sun6i_isp_params_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* Video Device */ + +static int sun6i_isp_params_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct video_device *video_dev = &isp_dev->params.video_dev; + + strscpy(capability->driver, SUN6I_ISP_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(isp_dev->dev)); + + return 0; +} + +static int sun6i_isp_params_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + struct v4l2_meta_format *params_format = + &isp_dev->params.format.fmt.meta; + + if (fmtdesc->index > 0) + return -EINVAL; + + fmtdesc->pixelformat = params_format->dataformat; + + return 0; +} + +static int sun6i_isp_params_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_isp_device *isp_dev = video_drvdata(file); + + *format = isp_dev->params.format; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_isp_params_ioctl_ops = { + .vidioc_querycap = sun6i_isp_params_querycap, + + .vidioc_enum_fmt_meta_out = sun6i_isp_params_enum_fmt, + .vidioc_g_fmt_meta_out = sun6i_isp_params_g_fmt, + .vidioc_s_fmt_meta_out = sun6i_isp_params_g_fmt, + .vidioc_try_fmt_meta_out = sun6i_isp_params_g_fmt, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static const struct v4l2_file_operations sun6i_isp_params_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +/* Params */ + +int sun6i_isp_params_setup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params *params = &isp_dev->params; + struct sun6i_isp_params_state *state = ¶ms->state; + struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev; + struct v4l2_subdev *proc_subdev = &isp_dev->proc.subdev; + struct video_device *video_dev = ¶ms->video_dev; + struct vb2_queue *queue = &isp_dev->params.queue; + struct media_pad *pad = &isp_dev->params.pad; + struct v4l2_format *format = &isp_dev->params.format; + struct v4l2_meta_format *params_format = &format->fmt.meta; + int ret; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Pads */ + + pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret) + goto error_mutex; + + /* Queue */ + + mutex_init(¶ms->lock); + + queue->type = V4L2_BUF_TYPE_META_OUTPUT; + queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_isp_buffer); + queue->ops = &sun6i_isp_params_queue_ops; + queue->mem_ops = &vb2_vmalloc_memops; + queue->min_buffers_needed = 1; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = ¶ms->lock; + queue->dev = isp_dev->dev; + queue->drv_priv = isp_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + params_format->dataformat = V4L2_META_FMT_SUN6I_ISP_PARAMS; + params_format->buffersize = sizeof(struct sun6i_isp_params_config); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_ISP_PARAMS_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_TX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_isp_params_fops; + video_dev->ioctl_ops = &sun6i_isp_params_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = ¶ms->lock; + + video_set_drvdata(video_dev, isp_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&video_dev->entity, 0, + &proc_subdev->entity, + SUN6I_ISP_PROC_PAD_SINK_PARAMS, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + video_dev->entity.name, 0, proc_subdev->entity.name, + SUN6I_ISP_PROC_PAD_SINK_PARAMS); + goto error_video_device; + } + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + +error_mutex: + mutex_destroy(¶ms->lock); + + return ret; +} + +void sun6i_isp_params_cleanup(struct sun6i_isp_device *isp_dev) +{ + struct sun6i_isp_params *params = &isp_dev->params; + struct video_device *video_dev = ¶ms->video_dev; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(¶ms->lock); +} |