// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2021-2022 Bootlin * Author: Paul Kocialkowski */ #include #include #include #include #include #include #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); state->pending = NULL; } 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); }