blob: 53d05e8a364bb7ed060a957034ef28ef91209fd1 [file] [log] [blame]
// 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);
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 = &params->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 = &params->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(&params->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_queued_buffers = 1;
queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
queue->lock = &params->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 = &params->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(&params->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 = &params->video_dev;
vb2_video_unregister_device(video_dev);
media_entity_cleanup(&video_dev->entity);
mutex_destroy(&params->lock);
}