Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
vsp1-y := vsp1_drv.o vsp1_entity.o vsp1_pipe.o
vsp1-y += vsp1_dl.o vsp1_drm.o vsp1_video.o
vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o
vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o
vsp1-y += vsp1_brx.o vsp1_sru.o vsp1_uds.o
vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o
vsp1-y += vsp1_iif.o vsp1_lif.o vsp1_uif.o
vsp1-y += vsp1_vspx.o
obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o
@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1.h -- R-Car VSP1 Driver
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_H__
#define __VSP1_H__
#include <linux/io.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <media/media-device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include "vsp1_regs.h"
struct clk;
struct device;
struct rcar_fcp_device;
struct reset_control;
struct vsp1_drm;
struct vsp1_entity;
struct vsp1_platform_data;
struct vsp1_brx;
struct vsp1_clu;
struct vsp1_hgo;
struct vsp1_hgt;
struct vsp1_hsit;
struct vsp1_iif;
struct vsp1_lif;
struct vsp1_lut;
struct vsp1_rwpf;
struct vsp1_sru;
struct vsp1_uds;
struct vsp1_uif;
#define VSP1_MAX_LIF 2
#define VSP1_MAX_RPF 5
#define VSP1_MAX_UDS 3
#define VSP1_MAX_UIF 2
#define VSP1_MAX_WPF 4
#define VSP1_HAS_LUT BIT(1)
#define VSP1_HAS_SRU BIT(2)
#define VSP1_HAS_BRU BIT(3)
#define VSP1_HAS_CLU BIT(4)
#define VSP1_HAS_WPF_VFLIP BIT(5)
#define VSP1_HAS_WPF_HFLIP BIT(6)
#define VSP1_HAS_HGO BIT(7)
#define VSP1_HAS_HGT BIT(8)
#define VSP1_HAS_BRS BIT(9)
#define VSP1_HAS_EXT_DL BIT(10)
#define VSP1_HAS_NON_ZERO_LBA BIT(11)
#define VSP1_HAS_IIF BIT(12)
#define VSP1_HAS_HSIT BIT(13)
struct vsp1_device_info {
u32 version;
const char *model;
unsigned int gen;
unsigned int features;
unsigned int lif_count;
unsigned int rpf_count;
unsigned int uds_count;
unsigned int uif_count;
unsigned int wpf_count;
unsigned int num_bru_inputs;
u8 soc;
bool uapi;
};
#define vsp1_feature(vsp1, f) ((vsp1)->info->features & (f))
struct vsp1_device {
struct device *dev;
const struct vsp1_device_info *info;
u32 version;
void __iomem *mmio;
struct rcar_fcp_device *fcp;
struct device *bus_master;
struct reset_control *rstc;
struct vsp1_brx *brs;
struct vsp1_brx *bru;
struct vsp1_clu *clu;
struct vsp1_hgo *hgo;
struct vsp1_hgt *hgt;
struct vsp1_hsit *hsi;
struct vsp1_hsit *hst;
struct vsp1_iif *iif;
struct vsp1_lif *lif[VSP1_MAX_LIF];
struct vsp1_lut *lut;
struct vsp1_rwpf *rpf[VSP1_MAX_RPF];
struct vsp1_sru *sru;
struct vsp1_uds *uds[VSP1_MAX_UDS];
struct vsp1_uif *uif[VSP1_MAX_UIF];
struct vsp1_rwpf *wpf[VSP1_MAX_WPF];
struct list_head entities;
struct list_head videos;
struct v4l2_device v4l2_dev;
struct media_device media_dev;
struct media_entity_operations media_ops;
struct vsp1_drm *drm;
struct vsp1_vspx *vspx;
};
int vsp1_device_get(struct vsp1_device *vsp1);
void vsp1_device_put(struct vsp1_device *vsp1);
int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index);
static inline u32 vsp1_read(struct vsp1_device *vsp1, u32 reg)
{
return ioread32(vsp1->mmio + reg);
}
static inline void vsp1_write(struct vsp1_device *vsp1, u32 reg, u32 data)
{
iowrite32(data, vsp1->mmio + reg);
}
#endif /* __VSP1_H__ */
@@ -0,0 +1,440 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_brx.c -- R-Car VSP1 Blend ROP Unit (BRU and BRS)
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_brx.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
#include "vsp1_video.h"
#define BRX_MIN_SIZE 1U
#define BRX_MAX_SIZE 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_brx_write(struct vsp1_brx *brx,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, brx->base + reg, data);
}
/* -----------------------------------------------------------------------------
* Controls
*/
static int brx_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_brx *brx =
container_of(ctrl->handler, struct vsp1_brx, ctrls);
switch (ctrl->id) {
case V4L2_CID_BG_COLOR:
brx->bgcolor = ctrl->val;
break;
}
return 0;
}
static const struct v4l2_ctrl_ops brx_ctrl_ops = {
.s_ctrl = brx_s_ctrl,
};
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int brx_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
/*
* The BRx can't perform format conversion, all sink and source formats must be
* identical. We pick the format on the first sink pad (pad 0) and propagate it
* to all other pads.
*/
static int brx_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
if (fse->index)
return -EINVAL;
if (fse->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
fse->code != MEDIA_BUS_FMT_AYUV8_1X32)
return -EINVAL;
fse->min_width = BRX_MIN_SIZE;
fse->max_width = BRX_MAX_SIZE;
fse->min_height = BRX_MIN_SIZE;
fse->max_height = BRX_MAX_SIZE;
return 0;
}
static void brx_try_format(struct vsp1_brx *brx,
struct v4l2_subdev_state *sd_state,
unsigned int pad, struct v4l2_mbus_framefmt *fmt)
{
struct v4l2_mbus_framefmt *format;
switch (pad) {
case BRX_PAD_SINK(0):
/* Default to YUV if the requested format is not supported. */
if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
fmt->code != MEDIA_BUS_FMT_AYUV8_1X32)
fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
vsp1_entity_adjust_color_space(fmt);
break;
default:
/* The BRx can't perform format conversion. */
format = v4l2_subdev_state_get_format(sd_state,
BRX_PAD_SINK(0));
fmt->code = format->code;
fmt->colorspace = format->colorspace;
fmt->xfer_func = format->xfer_func;
fmt->ycbcr_enc = format->ycbcr_enc;
fmt->quantization = format->quantization;
break;
}
fmt->width = clamp(fmt->width, BRX_MIN_SIZE, BRX_MAX_SIZE);
fmt->height = clamp(fmt->height, BRX_MIN_SIZE, BRX_MAX_SIZE);
fmt->field = V4L2_FIELD_NONE;
}
static int brx_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_brx *brx = to_brx(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
guard(mutex)(&brx->entity.lock);
state = vsp1_entity_get_state(&brx->entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
brx_try_format(brx, state, fmt->pad, &fmt->format);
format = v4l2_subdev_state_get_format(state, fmt->pad);
*format = fmt->format;
/* Reset the compose rectangle. */
if (fmt->pad != brx->entity.source_pad) {
struct v4l2_rect *compose;
compose = v4l2_subdev_state_get_compose(state, fmt->pad);
compose->left = 0;
compose->top = 0;
compose->width = format->width;
compose->height = format->height;
}
/* Propagate the format code to all pads. */
if (fmt->pad == BRX_PAD_SINK(0)) {
unsigned int i;
for (i = 0; i <= brx->entity.source_pad; ++i) {
format = v4l2_subdev_state_get_format(state, i);
format->code = fmt->format.code;
}
}
return 0;
}
static int brx_get_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_brx *brx = to_brx(subdev);
struct v4l2_subdev_state *state;
if (sel->pad == brx->entity.source_pad)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = BRX_MAX_SIZE;
sel->r.height = BRX_MAX_SIZE;
return 0;
case V4L2_SEL_TGT_COMPOSE:
state = vsp1_entity_get_state(&brx->entity, sd_state,
sel->which);
if (!state)
return -EINVAL;
scoped_guard(mutex, &brx->entity.lock) {
sel->r = *v4l2_subdev_state_get_compose(state, sel->pad);
}
return 0;
default:
return -EINVAL;
}
}
static int brx_set_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_brx *brx = to_brx(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *compose;
if (sel->pad == brx->entity.source_pad)
return -EINVAL;
if (sel->target != V4L2_SEL_TGT_COMPOSE)
return -EINVAL;
guard(mutex)(&brx->entity.lock);
state = vsp1_entity_get_state(&brx->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
/*
* The compose rectangle top left corner must be inside the output
* frame.
*/
format = v4l2_subdev_state_get_format(state, brx->entity.source_pad);
sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1);
sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1);
/*
* Scaling isn't supported, the compose rectangle size must be identical
* to the sink format size.
*/
format = v4l2_subdev_state_get_format(state, sel->pad);
sel->r.width = format->width;
sel->r.height = format->height;
compose = v4l2_subdev_state_get_compose(state, sel->pad);
*compose = sel->r;
return 0;
}
static const struct v4l2_subdev_pad_ops brx_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = brx_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = brx_set_format,
.get_selection = brx_get_selection,
.set_selection = brx_set_selection,
};
static const struct v4l2_subdev_ops brx_ops = {
.core = &vsp1_entity_core_ops,
.pad = &brx_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void brx_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_brx *brx = to_brx(&entity->subdev);
struct v4l2_mbus_framefmt *format;
unsigned int flags;
unsigned int i;
format = v4l2_subdev_state_get_format(state, brx->entity.source_pad);
/*
* The hardware is extremely flexible but we have no userspace API to
* expose all the parameters, nor is it clear whether we would have use
* cases for all the supported modes. Let's just hardcode the parameters
* to sane default values for now.
*/
/*
* Disable dithering and enable color data normalization unless the
* format at the pipeline output is premultiplied.
*/
flags = pipe->output ? pipe->output->format.flags : 0;
vsp1_brx_write(brx, dlb, VI6_BRU_INCTRL,
flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA ?
0 : VI6_BRU_INCTRL_NRM);
/*
* Set the background position to cover the whole output image and
* configure its color.
*/
vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_SIZE,
(format->width << VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT) |
(format->height << VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT));
vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_LOC, 0);
vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_COL, brx->bgcolor |
(0xff << VI6_BRU_VIRRPF_COL_A_SHIFT));
/*
* Route BRU input 1 as SRC input to the ROP unit and configure the ROP
* unit with a NOP operation to make BRU input 1 available as the
* Blend/ROP unit B SRC input. Only needed for BRU, the BRS has no ROP
* unit.
*/
if (entity->type == VSP1_ENTITY_BRU)
vsp1_brx_write(brx, dlb, VI6_BRU_ROP,
VI6_BRU_ROP_DSTSEL_BRUIN(1) |
VI6_BRU_ROP_CROP(VI6_ROP_NOP) |
VI6_BRU_ROP_AROP(VI6_ROP_NOP));
for (i = 0; i < brx->entity.source_pad; ++i) {
bool premultiplied = false;
u32 ctrl = 0;
/*
* Configure all Blend/ROP units corresponding to an enabled BRx
* input for alpha blending. Blend/ROP units corresponding to
* disabled BRx inputs are used in ROP NOP mode to ignore the
* SRC input.
*/
if (brx->inputs[i].rpf) {
ctrl |= VI6_BRU_CTRL_RBC;
premultiplied = brx->inputs[i].rpf->format.flags
& V4L2_PIX_FMT_FLAG_PREMUL_ALPHA;
} else {
ctrl |= VI6_BRU_CTRL_CROP(VI6_ROP_NOP)
| VI6_BRU_CTRL_AROP(VI6_ROP_NOP);
}
/*
* Select the virtual RPF as the Blend/ROP unit A DST input to
* serve as a background color.
*/
if (i == 0)
ctrl |= VI6_BRU_CTRL_DSTSEL_VRPF;
/*
* Route inputs 0 to 3 as SRC inputs to Blend/ROP units A to D
* in that order. In the BRU the Blend/ROP unit B SRC is
* hardwired to the ROP unit output, the corresponding register
* bits must be set to 0. The BRS has no ROP unit and doesn't
* need any special processing.
*/
if (!(entity->type == VSP1_ENTITY_BRU && i == 1))
ctrl |= VI6_BRU_CTRL_SRCSEL_BRUIN(i);
vsp1_brx_write(brx, dlb, VI6_BRU_CTRL(i), ctrl);
/*
* Hardcode the blending formula to
*
* DSTc = DSTc * (1 - SRCa) + SRCc * SRCa
* DSTa = DSTa * (1 - SRCa) + SRCa
*
* when the SRC input isn't premultiplied, and to
*
* DSTc = DSTc * (1 - SRCa) + SRCc
* DSTa = DSTa * (1 - SRCa) + SRCa
*
* otherwise.
*/
vsp1_brx_write(brx, dlb, VI6_BRU_BLD(i),
VI6_BRU_BLD_CCMDX_255_SRC_A |
(premultiplied ? VI6_BRU_BLD_CCMDY_COEFY :
VI6_BRU_BLD_CCMDY_SRC_A) |
VI6_BRU_BLD_ACMDX_255_SRC_A |
VI6_BRU_BLD_ACMDY_COEFY |
(0xff << VI6_BRU_BLD_COEFY_SHIFT));
}
}
static const struct vsp1_entity_operations brx_entity_ops = {
.configure_stream = brx_configure_stream,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1,
enum vsp1_entity_type type)
{
struct vsp1_brx *brx;
unsigned int num_pads;
const char *name;
int ret;
brx = devm_kzalloc(vsp1->dev, sizeof(*brx), GFP_KERNEL);
if (brx == NULL)
return ERR_PTR(-ENOMEM);
brx->base = type == VSP1_ENTITY_BRU ? VI6_BRU_BASE : VI6_BRS_BASE;
brx->entity.ops = &brx_entity_ops;
brx->entity.type = type;
brx->entity.codes = brx_codes;
brx->entity.num_codes = ARRAY_SIZE(brx_codes);
brx->entity.min_width = BRX_MIN_SIZE;
brx->entity.max_width = BRX_MAX_SIZE;
brx->entity.min_height = BRX_MIN_SIZE;
brx->entity.max_height = BRX_MAX_SIZE;
if (type == VSP1_ENTITY_BRU) {
num_pads = vsp1->info->num_bru_inputs + 1;
name = "bru";
} else {
num_pads = 3;
name = "brs";
}
ret = vsp1_entity_init(vsp1, &brx->entity, name, num_pads, &brx_ops,
MEDIA_ENT_F_PROC_VIDEO_COMPOSER);
if (ret < 0)
return ERR_PTR(ret);
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&brx->ctrls, 1);
v4l2_ctrl_new_std(&brx->ctrls, &brx_ctrl_ops, V4L2_CID_BG_COLOR,
0, 0xffffff, 1, 0);
brx->bgcolor = 0;
brx->entity.subdev.ctrl_handler = &brx->ctrls;
if (brx->ctrls.error) {
dev_err(vsp1->dev, "%s: failed to initialize controls\n", name);
ret = brx->ctrls.error;
vsp1_entity_destroy(&brx->entity);
return ERR_PTR(ret);
}
return brx;
}
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_brx.h -- R-Car VSP1 Blend ROP Unit (BRU and BRS)
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_BRX_H__
#define __VSP1_BRX_H__
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
struct vsp1_rwpf;
#define BRX_PAD_SINK(n) (n)
struct vsp1_brx {
struct vsp1_entity entity;
unsigned int base;
struct v4l2_ctrl_handler ctrls;
struct {
struct vsp1_rwpf *rpf;
} inputs[VSP1_MAX_RPF];
u32 bgcolor;
};
static inline struct vsp1_brx *to_brx(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_brx, entity.subdev);
}
struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1,
enum vsp1_entity_type type);
#endif /* __VSP1_BRX_H__ */
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_clu.c -- R-Car VSP1 Cubic Look-Up Table
*
* Copyright (C) 2015-2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/slab.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_clu.h"
#include "vsp1_dl.h"
#define CLU_MIN_SIZE 4U
#define CLU_MAX_SIZE 8190U
#define CLU_SIZE (17 * 17 * 17)
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_clu_write(struct vsp1_clu *clu,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* Controls
*/
#define V4L2_CID_VSP1_CLU_TABLE (V4L2_CID_USER_BASE | 0x1001)
#define V4L2_CID_VSP1_CLU_MODE (V4L2_CID_USER_BASE | 0x1002)
#define V4L2_CID_VSP1_CLU_MODE_2D 0
#define V4L2_CID_VSP1_CLU_MODE_3D 1
static int clu_set_table(struct vsp1_clu *clu, struct v4l2_ctrl *ctrl)
{
struct vsp1_dl_body *dlb;
unsigned int i;
dlb = vsp1_dl_body_get(clu->pool);
if (!dlb)
return -ENOMEM;
vsp1_dl_body_write(dlb, VI6_CLU_ADDR, 0);
for (i = 0; i < CLU_SIZE; ++i)
vsp1_dl_body_write(dlb, VI6_CLU_DATA, ctrl->p_new.p_u32[i]);
scoped_guard(spinlock_irq, &clu->lock) {
swap(clu->clu, dlb);
}
vsp1_dl_body_put(dlb);
return 0;
}
static int clu_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_clu *clu =
container_of(ctrl->handler, struct vsp1_clu, ctrls);
switch (ctrl->id) {
case V4L2_CID_VSP1_CLU_TABLE:
clu_set_table(clu, ctrl);
break;
case V4L2_CID_VSP1_CLU_MODE:
clu->mode = ctrl->val;
break;
}
return 0;
}
static const struct v4l2_ctrl_ops clu_ctrl_ops = {
.s_ctrl = clu_s_ctrl,
};
static const struct v4l2_ctrl_config clu_table_control = {
.ops = &clu_ctrl_ops,
.id = V4L2_CID_VSP1_CLU_TABLE,
.name = "Look-Up Table",
.type = V4L2_CTRL_TYPE_U32,
.min = 0x00000000,
.max = 0x00ffffff,
.step = 1,
.def = 0,
.dims = { 17, 17, 17 },
};
static const char * const clu_mode_menu[] = {
"2D",
"3D",
NULL,
};
static const struct v4l2_ctrl_config clu_mode_control = {
.ops = &clu_ctrl_ops,
.id = V4L2_CID_VSP1_CLU_MODE,
.name = "Mode",
.type = V4L2_CTRL_TYPE_MENU,
.min = 0,
.max = 1,
.def = 1,
.qmenu = clu_mode_menu,
};
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int clu_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static const struct v4l2_subdev_pad_ops clu_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = vsp1_subdev_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_subdev_set_pad_format,
};
static const struct v4l2_subdev_ops clu_ops = {
.core = &vsp1_entity_core_ops,
.pad = &clu_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void clu_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_clu *clu = to_clu(&entity->subdev);
struct v4l2_mbus_framefmt *format;
/*
* The yuv_mode can't be changed during streaming. Cache it internally
* for future runtime configuration calls.
*/
format = v4l2_subdev_state_get_format(state, CLU_PAD_SINK);
clu->yuv_mode = format->code == MEDIA_BUS_FMT_AYUV8_1X32;
}
static void clu_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_clu *clu = to_clu(&entity->subdev);
struct vsp1_dl_body *clu_dlb;
u32 ctrl = VI6_CLU_CTRL_AAI | VI6_CLU_CTRL_MVS | VI6_CLU_CTRL_EN;
/* 2D mode can only be used with the YCbCr pixel encoding. */
if (clu->mode == V4L2_CID_VSP1_CLU_MODE_2D && clu->yuv_mode)
ctrl |= VI6_CLU_CTRL_AX1I_2D | VI6_CLU_CTRL_AX2I_2D
| VI6_CLU_CTRL_OS0_2D | VI6_CLU_CTRL_OS1_2D
| VI6_CLU_CTRL_OS2_2D | VI6_CLU_CTRL_M2D;
vsp1_clu_write(clu, dlb, VI6_CLU_CTRL, ctrl);
scoped_guard(spinlock_irqsave, &clu->lock) {
clu_dlb = clu->clu;
clu->clu = NULL;
}
if (clu_dlb) {
vsp1_dl_list_add_body(dl, clu_dlb);
/* Release our local reference. */
vsp1_dl_body_put(clu_dlb);
}
}
static void clu_destroy(struct vsp1_entity *entity)
{
struct vsp1_clu *clu = to_clu(&entity->subdev);
vsp1_dl_body_pool_destroy(clu->pool);
}
static const struct vsp1_entity_operations clu_entity_ops = {
.configure_stream = clu_configure_stream,
.configure_frame = clu_configure_frame,
.destroy = clu_destroy,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_clu *vsp1_clu_create(struct vsp1_device *vsp1)
{
struct vsp1_clu *clu;
int ret;
clu = devm_kzalloc(vsp1->dev, sizeof(*clu), GFP_KERNEL);
if (clu == NULL)
return ERR_PTR(-ENOMEM);
spin_lock_init(&clu->lock);
clu->entity.ops = &clu_entity_ops;
clu->entity.type = VSP1_ENTITY_CLU;
clu->entity.codes = clu_codes;
clu->entity.num_codes = ARRAY_SIZE(clu_codes);
clu->entity.min_width = CLU_MIN_SIZE;
clu->entity.min_height = CLU_MIN_SIZE;
clu->entity.max_width = CLU_MAX_SIZE;
clu->entity.max_height = CLU_MAX_SIZE;
ret = vsp1_entity_init(vsp1, &clu->entity, "clu", 2, &clu_ops,
MEDIA_ENT_F_PROC_VIDEO_LUT);
if (ret < 0)
return ERR_PTR(ret);
/*
* Pre-allocate a body pool, with 3 bodies allowing a userspace update
* before the hardware has committed a previous set of tables, handling
* both the queued and pending dl entries. One extra entry is added to
* the CLU_SIZE to allow for the VI6_CLU_ADDR header.
*/
clu->pool = vsp1_dl_body_pool_create(clu->entity.vsp1, 3, CLU_SIZE + 1,
0);
if (!clu->pool)
return ERR_PTR(-ENOMEM);
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&clu->ctrls, 2);
v4l2_ctrl_new_custom(&clu->ctrls, &clu_table_control, NULL);
v4l2_ctrl_new_custom(&clu->ctrls, &clu_mode_control, NULL);
clu->entity.subdev.ctrl_handler = &clu->ctrls;
if (clu->ctrls.error) {
dev_err(vsp1->dev, "clu: failed to initialize controls\n");
ret = clu->ctrls.error;
vsp1_entity_destroy(&clu->entity);
return ERR_PTR(ret);
}
v4l2_ctrl_handler_setup(&clu->ctrls);
return clu;
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_clu.h -- R-Car VSP1 Cubic Look-Up Table
*
* Copyright (C) 2015 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_CLU_H__
#define __VSP1_CLU_H__
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
struct vsp1_dl_body;
#define CLU_PAD_SINK 0
#define CLU_PAD_SOURCE 1
struct vsp1_clu {
struct vsp1_entity entity;
struct v4l2_ctrl_handler ctrls;
bool yuv_mode;
spinlock_t lock;
unsigned int mode;
struct vsp1_dl_body *clu;
struct vsp1_dl_body_pool *pool;
};
static inline struct vsp1_clu *to_clu(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_clu, entity.subdev);
}
struct vsp1_clu *vsp1_clu_create(struct vsp1_device *vsp1);
#endif /* __VSP1_CLU_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_dl.h -- R-Car VSP1 Display List
*
* Copyright (C) 2015 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_DL_H__
#define __VSP1_DL_H__
#include <linux/types.h>
struct vsp1_device;
struct vsp1_dl_body;
struct vsp1_dl_body_pool;
struct vsp1_dl_list;
struct vsp1_dl_manager;
/* Keep these flags in sync with VSP1_DU_STATUS_* in include/media/vsp1.h. */
#define VSP1_DL_FRAME_END_COMPLETED BIT(0)
#define VSP1_DL_FRAME_END_WRITEBACK BIT(1)
#define VSP1_DL_FRAME_END_INTERNAL BIT(2)
/**
* struct vsp1_dl_ext_cmd - Extended Display command
* @pool: pool to which this command belongs
* @free: entry in the pool of free commands list
* @opcode: command type opcode
* @flags: flags used by the command
* @cmds: array of command bodies for this extended cmd
* @num_cmds: quantity of commands in @cmds array
* @cmd_dma: DMA address of the command body
* @data: memory allocation for command-specific data
* @data_dma: DMA address for command-specific data
*/
struct vsp1_dl_ext_cmd {
struct vsp1_dl_cmd_pool *pool;
struct list_head free;
u8 opcode;
u32 flags;
struct vsp1_pre_ext_dl_body *cmds;
unsigned int num_cmds;
dma_addr_t cmd_dma;
void *data;
dma_addr_t data_dma;
};
void vsp1_dlm_setup(struct vsp1_device *vsp1);
struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1,
unsigned int index,
unsigned int prealloc);
void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm);
void vsp1_dlm_reset(struct vsp1_dl_manager *dlm);
unsigned int vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm);
struct vsp1_dl_body *vsp1_dlm_dl_body_get(struct vsp1_dl_manager *dlm);
struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm);
void vsp1_dl_list_put(struct vsp1_dl_list *dl);
struct vsp1_dl_body *vsp1_dl_list_get_body0(struct vsp1_dl_list *dl);
struct vsp1_dl_ext_cmd *vsp1_dl_get_pre_cmd(struct vsp1_dl_list *dl);
void vsp1_dl_list_commit(struct vsp1_dl_list *dl, unsigned int dl_flags);
struct vsp1_dl_body_pool *
vsp1_dl_body_pool_create(struct vsp1_device *vsp1, unsigned int num_bodies,
unsigned int num_entries, size_t extra_size);
void vsp1_dl_body_pool_destroy(struct vsp1_dl_body_pool *pool);
struct vsp1_dl_body *vsp1_dl_body_get(struct vsp1_dl_body_pool *pool);
void vsp1_dl_body_put(struct vsp1_dl_body *dlb);
void vsp1_dl_body_write(struct vsp1_dl_body *dlb, u32 reg, u32 data);
int vsp1_dl_list_add_body(struct vsp1_dl_list *dl, struct vsp1_dl_body *dlb);
int vsp1_dl_list_add_chain(struct vsp1_dl_list *head, struct vsp1_dl_list *dl);
#endif /* __VSP1_DL_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_drm.h -- R-Car VSP1 DRM/KMS Interface
*
* Copyright (C) 2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_DRM_H__
#define __VSP1_DRM_H__
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/wait.h>
#include <media/vsp1.h>
#include "vsp1_pipe.h"
/**
* struct vsp1_drm_pipeline - State for the API exposed to the DRM driver
* @pipe: the VSP1 pipeline used for display
* @partition: the pre-calculated partition used by the pipeline
* @width: output display width
* @height: output display height
* @force_brx_release: when set, release the BRx during the next reconfiguration
* @wait_queue: wait queue to wait for BRx release completion
* @uif: UIF entity if available for the pipeline
* @crc: CRC computation configuration
* @du_complete: frame completion callback for the DU driver (optional)
* @du_private: data to be passed to the du_complete callback
*/
struct vsp1_drm_pipeline {
struct vsp1_pipeline pipe;
struct vsp1_partition partition;
unsigned int width;
unsigned int height;
bool force_brx_release;
wait_queue_head_t wait_queue;
struct vsp1_entity *uif;
struct vsp1_du_crc_config crc;
/* Frame synchronisation */
void (*du_complete)(void *data, unsigned int status, u32 crc);
void *du_private;
};
/**
* struct vsp1_drm - State for the API exposed to the DRM driver
* @pipe: the VSP1 DRM pipeline used for display
* @lock: protects the BRU and BRS allocation
* @inputs: source crop rectangle, destination compose rectangle, z-order
* position and colorspace for every input (indexed by RPF index)
*/
struct vsp1_drm {
struct vsp1_drm_pipeline pipe[VSP1_MAX_LIF];
struct mutex lock;
struct vsp1_drm_input {
struct v4l2_rect crop;
struct v4l2_rect compose;
unsigned int zpos;
enum v4l2_ycbcr_encoding ycbcr_enc;
enum v4l2_quantization quantization;
} inputs[VSP1_MAX_RPF];
};
static inline struct vsp1_drm_pipeline *
to_vsp1_drm_pipeline(struct vsp1_pipeline *pipe)
{
return container_of(pipe, struct vsp1_drm_pipeline, pipe);
}
int vsp1_drm_init(struct vsp1_device *vsp1);
void vsp1_drm_cleanup(struct vsp1_device *vsp1);
#endif /* __VSP1_DRM_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,677 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_entity.c -- R-Car VSP1 Base Entity
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/mutex.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
void vsp1_entity_route_setup(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_body *dlb)
{
struct vsp1_entity *source;
u32 route;
if (entity->type == VSP1_ENTITY_HGO) {
u32 smppt;
/*
* The HGO is a special case, its routing is configured on the
* sink pad.
*/
source = entity->sources[0];
smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT)
| (source->route->output << VI6_DPR_SMPPT_PT_SHIFT);
vsp1_dl_body_write(dlb, VI6_DPR_HGO_SMPPT, smppt);
return;
} else if (entity->type == VSP1_ENTITY_HGT) {
u32 smppt;
/*
* The HGT is a special case, its routing is configured on the
* sink pad.
*/
source = entity->sources[0];
smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT)
| (source->route->output << VI6_DPR_SMPPT_PT_SHIFT);
vsp1_dl_body_write(dlb, VI6_DPR_HGT_SMPPT, smppt);
return;
}
source = entity;
if (source->route->reg == 0)
return;
route = source->sink->route->inputs[source->sink_pad];
/*
* The ILV and BRS share the same data path route. The extra BRSSEL bit
* selects between the ILV and BRS.
*
* The BRU and IIF share the same data path route. The extra IIFSEL bit
* selects between the IIF and BRU.
*/
if (source->type == VSP1_ENTITY_BRS)
route |= VI6_DPR_ROUTE_BRSSEL;
else if (source->type == VSP1_ENTITY_IIF)
route |= VI6_DPR_ROUTE_IIFSEL;
vsp1_dl_body_write(dlb, source->route->reg, route);
}
void vsp1_entity_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
if (entity->ops->configure_stream)
entity->ops->configure_stream(entity, state, pipe, dl, dlb);
}
void vsp1_entity_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
if (entity->ops->configure_frame)
entity->ops->configure_frame(entity, pipe, dl, dlb);
}
void vsp1_entity_configure_partition(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
if (entity->ops->configure_partition)
entity->ops->configure_partition(entity, pipe, partition,
dl, dlb);
}
void vsp1_entity_adjust_color_space(struct v4l2_mbus_framefmt *format)
{
u8 xfer_func = format->xfer_func;
u8 ycbcr_enc = format->ycbcr_enc;
u8 quantization = format->quantization;
vsp1_adjust_color_space(format->code, &format->colorspace, &xfer_func,
&ycbcr_enc, &quantization);
format->xfer_func = xfer_func;
format->ycbcr_enc = ycbcr_enc;
format->quantization = quantization;
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
/**
* vsp1_entity_get_state - Get the subdev state for an entity
* @entity: the entity
* @sd_state: the TRY state
* @which: state selector (ACTIVE or TRY)
*
* When called with which set to V4L2_SUBDEV_FORMAT_ACTIVE the caller must hold
* the entity lock to access the returned configuration.
*
* Return the subdev state requested by the which argument. The TRY state is
* passed explicitly to the function through the sd_state argument and simply
* returned when requested. The ACTIVE state comes from the entity structure.
*/
struct v4l2_subdev_state *
vsp1_entity_get_state(struct vsp1_entity *entity,
struct v4l2_subdev_state *sd_state,
enum v4l2_subdev_format_whence which)
{
switch (which) {
case V4L2_SUBDEV_FORMAT_ACTIVE:
return entity->state;
case V4L2_SUBDEV_FORMAT_TRY:
default:
return sd_state;
}
}
/*
* vsp1_subdev_get_pad_format - Subdev pad get_fmt handler
* @subdev: V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fmt: V4L2 subdev format
*
* This function implements the subdev get_fmt pad operation. It can be used as
* a direct drop-in for the operation handler.
*/
int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct v4l2_subdev_state *state;
state = vsp1_entity_get_state(entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
guard(mutex)(&entity->lock);
fmt->format = *v4l2_subdev_state_get_format(state, fmt->pad);
return 0;
}
/*
* vsp1_subdev_enum_mbus_code - Subdev pad enum_mbus_code handler
* @subdev: V4L2 subdevice
* @sd_state: V4L2 subdev state
* @code: Media bus code enumeration
*
* This function implements the subdev enum_mbus_code pad operation for entities
* that do not support format conversion. It enumerates the given supported
* media bus codes on the sink pad and reports a source pad format identical to
* the sink pad.
*/
int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
if (code->pad == 0) {
if (code->index >= entity->num_codes)
return -EINVAL;
code->code = entity->codes[code->index];
} else {
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
/*
* The entity can't perform format conversion, the sink format
* is always identical to the source format.
*/
if (code->index)
return -EINVAL;
state = vsp1_entity_get_state(entity, sd_state, code->which);
if (!state)
return -EINVAL;
scoped_guard(mutex, &entity->lock) {
format = v4l2_subdev_state_get_format(state, 0);
code->code = format->code;
}
}
return 0;
}
/*
* vsp1_subdev_enum_frame_size - Subdev pad enum_frame_size handler
* @subdev: V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fse: Frame size enumeration
*
* This function implements the subdev enum_frame_size pad operation for
* entities that do not support scaling or cropping. It reports the given
* minimum and maximum frame width and height on the sink pad, and a fixed
* source pad size identical to the sink pad.
*/
int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
if (fse->index)
return -EINVAL;
if (fse->pad == 0) {
unsigned int i;
for (i = 0; i < entity->num_codes; ++i) {
if (fse->code == entity->codes[i])
break;
}
if (i == entity->num_codes)
return -EINVAL;
fse->min_width = entity->min_width;
fse->max_width = entity->max_width;
fse->min_height = entity->min_height;
fse->max_height = entity->max_height;
} else {
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
state = vsp1_entity_get_state(entity, sd_state, fse->which);
if (!state)
return -EINVAL;
/*
* The media bus code and size on the source pad are fixed and
* always identical to the sink pad.
*/
format = v4l2_subdev_state_get_format(state, 0);
guard(mutex)(&entity->lock);
if (fse->code != format->code)
return -EINVAL;
fse->min_width = format->width;
fse->max_width = format->width;
fse->min_height = format->height;
fse->max_height = format->height;
}
return 0;
}
/*
* vsp1_subdev_set_pad_format - Subdev pad set_fmt handler
* @subdev: V4L2 subdevice
* @sd_state: V4L2 subdev state
* @fmt: V4L2 subdev format
*
* This function implements the subdev set_fmt pad operation for entities that
* do not support scaling or cropping. It defaults to the first supported media
* bus code if the requested code isn't supported, clamps the size to the
* entity's limits, and propagates the sink pad format to the source pad.
*/
int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *selection;
unsigned int i;
guard(mutex)(&entity->lock);
state = vsp1_entity_get_state(entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, fmt->pad);
if (fmt->pad == entity->source_pad) {
/* The output format can't be modified. */
fmt->format = *format;
return 0;
}
/*
* Default to the first media bus code if the requested format is not
* supported.
*/
for (i = 0; i < entity->num_codes; ++i) {
if (fmt->format.code == entity->codes[i])
break;
}
format->code = i < entity->num_codes
? entity->codes[i] : entity->codes[0];
format->width = clamp_t(unsigned int, fmt->format.width,
entity->min_width, entity->max_width);
format->height = clamp_t(unsigned int, fmt->format.height,
entity->min_height, entity->max_height);
format->field = V4L2_FIELD_NONE;
format->colorspace = fmt->format.colorspace;
format->xfer_func = fmt->format.xfer_func;
format->ycbcr_enc = fmt->format.ycbcr_enc;
format->quantization = fmt->format.quantization;
vsp1_entity_adjust_color_space(format);
fmt->format = *format;
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, entity->source_pad);
*format = fmt->format;
/* Reset the crop and compose rectangles. */
selection = v4l2_subdev_state_get_crop(state, fmt->pad);
selection->left = 0;
selection->top = 0;
selection->width = format->width;
selection->height = format->height;
selection = v4l2_subdev_state_get_compose(state, fmt->pad);
selection->left = 0;
selection->top = 0;
selection->width = format->width;
selection->height = format->height;
return 0;
}
static int vsp1_entity_init_state(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state)
{
unsigned int pad;
/* Initialize all pad formats with default values. */
for (pad = 0; pad < subdev->entity.num_pads - 1; ++pad) {
struct v4l2_subdev_format format = {
.pad = pad,
.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY
: V4L2_SUBDEV_FORMAT_ACTIVE,
};
v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &format);
}
return 0;
}
static const struct v4l2_subdev_internal_ops vsp1_entity_internal_ops = {
.init_state = vsp1_entity_init_state,
};
const struct v4l2_subdev_core_ops vsp1_entity_core_ops = {
.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};
/* -----------------------------------------------------------------------------
* Media Operations
*/
static inline struct vsp1_entity *
media_entity_to_vsp1_entity(struct media_entity *entity)
{
return container_of(entity, struct vsp1_entity, subdev.entity);
}
static int vsp1_entity_link_setup_source(const struct media_pad *source_pad,
const struct media_pad *sink_pad,
u32 flags)
{
struct vsp1_entity *source;
source = media_entity_to_vsp1_entity(source_pad->entity);
if (!source->route)
return 0;
if (flags & MEDIA_LNK_FL_ENABLED) {
struct vsp1_entity *sink
= media_entity_to_vsp1_entity(sink_pad->entity);
/*
* Fan-out is limited to one for the normal data path plus
* optional HGO and HGT. We ignore the HGO and HGT here.
*/
if (sink->type != VSP1_ENTITY_HGO &&
sink->type != VSP1_ENTITY_HGT) {
if (source->sink)
return -EBUSY;
source->sink = sink;
source->sink_pad = sink_pad->index;
}
} else {
source->sink = NULL;
source->sink_pad = 0;
}
return 0;
}
static int vsp1_entity_link_setup_sink(const struct media_pad *source_pad,
const struct media_pad *sink_pad,
u32 flags)
{
struct vsp1_entity *sink;
struct vsp1_entity *source;
sink = media_entity_to_vsp1_entity(sink_pad->entity);
source = media_entity_to_vsp1_entity(source_pad->entity);
if (flags & MEDIA_LNK_FL_ENABLED) {
/* Fan-in is limited to one. */
if (sink->sources[sink_pad->index])
return -EBUSY;
sink->sources[sink_pad->index] = source;
} else {
sink->sources[sink_pad->index] = NULL;
}
return 0;
}
int vsp1_entity_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags)
{
if (local->flags & MEDIA_PAD_FL_SOURCE)
return vsp1_entity_link_setup_source(local, remote, flags);
else
return vsp1_entity_link_setup_sink(remote, local, flags);
}
/**
* vsp1_entity_remote_pad - Find the pad at the remote end of a link
* @pad: Pad at the local end of the link
*
* Search for a remote pad connected to the given pad by iterating over all
* links originating or terminating at that pad until an enabled link is found.
*
* Our link setup implementation guarantees that the output fan-out will not be
* higher than one for the data pipelines, except for the links to the HGO and
* HGT that can be enabled in addition to a regular data link. When traversing
* outgoing links this function ignores HGO and HGT entities and should thus be
* used in place of the generic media_pad_remote_pad_first() function to
* traverse data pipelines.
*
* Return a pointer to the pad at the remote end of the first found enabled
* link, or NULL if no enabled link has been found.
*/
struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad)
{
struct media_link *link;
list_for_each_entry(link, &pad->entity->links, list) {
struct vsp1_entity *entity;
if (!(link->flags & MEDIA_LNK_FL_ENABLED))
continue;
/* If we're the sink the source will never be an HGO or HGT. */
if (link->sink == pad)
return link->source;
if (link->source != pad)
continue;
/* If the sink isn't a subdevice it can't be an HGO or HGT. */
if (!is_media_entity_v4l2_subdev(link->sink->entity))
return link->sink;
entity = media_entity_to_vsp1_entity(link->sink->entity);
if (entity->type != VSP1_ENTITY_HGO &&
entity->type != VSP1_ENTITY_HGT)
return link->sink;
}
return NULL;
}
/* -----------------------------------------------------------------------------
* Initialization
*/
#define VSP1_ENTITY_ROUTE(ent) \
{ VSP1_ENTITY_##ent, 0, VI6_DPR_##ent##_ROUTE, \
{ VI6_DPR_NODE_##ent }, VI6_DPR_NODE_##ent }
#define VSP1_ENTITY_ROUTE_RPF(idx) \
{ VSP1_ENTITY_RPF, idx, VI6_DPR_RPF_ROUTE(idx), \
{ 0, }, VI6_DPR_NODE_RPF(idx) }
#define VSP1_ENTITY_ROUTE_UDS(idx) \
{ VSP1_ENTITY_UDS, idx, VI6_DPR_UDS_ROUTE(idx), \
{ VI6_DPR_NODE_UDS(idx) }, VI6_DPR_NODE_UDS(idx) }
#define VSP1_ENTITY_ROUTE_UIF(idx) \
{ VSP1_ENTITY_UIF, idx, VI6_DPR_UIF_ROUTE(idx), \
{ VI6_DPR_NODE_UIF(idx) }, VI6_DPR_NODE_UIF(idx) }
#define VSP1_ENTITY_ROUTE_WPF(idx) \
{ VSP1_ENTITY_WPF, idx, 0, \
{ VI6_DPR_NODE_WPF(idx) }, VI6_DPR_NODE_WPF(idx) }
static const struct vsp1_route vsp1_routes[] = {
{ VSP1_ENTITY_IIF, 0, VI6_DPR_BRU_ROUTE,
{ VI6_DPR_NODE_BRU_IN(0), VI6_DPR_NODE_BRU_IN(1),
VI6_DPR_NODE_BRU_IN(3) }, VI6_DPR_NODE_WPF(0) },
{ VSP1_ENTITY_BRS, 0, VI6_DPR_ILV_BRS_ROUTE,
{ VI6_DPR_NODE_BRS_IN(0), VI6_DPR_NODE_BRS_IN(1) }, 0 },
{ VSP1_ENTITY_BRU, 0, VI6_DPR_BRU_ROUTE,
{ VI6_DPR_NODE_BRU_IN(0), VI6_DPR_NODE_BRU_IN(1),
VI6_DPR_NODE_BRU_IN(2), VI6_DPR_NODE_BRU_IN(3),
VI6_DPR_NODE_BRU_IN(4) }, VI6_DPR_NODE_BRU_OUT },
VSP1_ENTITY_ROUTE(CLU),
{ VSP1_ENTITY_HGO, 0, 0, { 0, }, 0 },
{ VSP1_ENTITY_HGT, 0, 0, { 0, }, 0 },
VSP1_ENTITY_ROUTE(HSI),
VSP1_ENTITY_ROUTE(HST),
{ VSP1_ENTITY_LIF, 0, 0, { 0, }, 0 },
{ VSP1_ENTITY_LIF, 1, 0, { 0, }, 0 },
VSP1_ENTITY_ROUTE(LUT),
VSP1_ENTITY_ROUTE_RPF(0),
VSP1_ENTITY_ROUTE_RPF(1),
VSP1_ENTITY_ROUTE_RPF(2),
VSP1_ENTITY_ROUTE_RPF(3),
VSP1_ENTITY_ROUTE_RPF(4),
VSP1_ENTITY_ROUTE(SRU),
VSP1_ENTITY_ROUTE_UDS(0),
VSP1_ENTITY_ROUTE_UDS(1),
VSP1_ENTITY_ROUTE_UDS(2),
VSP1_ENTITY_ROUTE_UIF(0), /* Named UIF4 in the documentation */
VSP1_ENTITY_ROUTE_UIF(1), /* Named UIF5 in the documentation */
VSP1_ENTITY_ROUTE_WPF(0),
VSP1_ENTITY_ROUTE_WPF(1),
VSP1_ENTITY_ROUTE_WPF(2),
VSP1_ENTITY_ROUTE_WPF(3),
};
int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity,
const char *name, unsigned int num_pads,
const struct v4l2_subdev_ops *ops, u32 function)
{
static struct lock_class_key key;
struct v4l2_subdev *subdev;
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(vsp1_routes); ++i) {
if (vsp1_routes[i].type == entity->type &&
vsp1_routes[i].index == entity->index) {
entity->route = &vsp1_routes[i];
break;
}
}
if (i == ARRAY_SIZE(vsp1_routes))
return -EINVAL;
mutex_init(&entity->lock);
entity->vsp1 = vsp1;
entity->source_pad = num_pads - 1;
/* Allocate and initialize pads. */
entity->pads = devm_kcalloc(vsp1->dev,
num_pads, sizeof(*entity->pads),
GFP_KERNEL);
if (entity->pads == NULL)
return -ENOMEM;
for (i = 0; i < num_pads - 1; ++i)
entity->pads[i].flags = MEDIA_PAD_FL_SINK;
entity->sources = devm_kcalloc(vsp1->dev, max(num_pads - 1, 1U),
sizeof(*entity->sources), GFP_KERNEL);
if (entity->sources == NULL)
return -ENOMEM;
/* Single-pad entities only have a sink. */
entity->pads[num_pads - 1].flags = num_pads > 1 ? MEDIA_PAD_FL_SOURCE
: MEDIA_PAD_FL_SINK;
/* Initialize the media entity. */
ret = media_entity_pads_init(&entity->subdev.entity, num_pads,
entity->pads);
if (ret < 0)
return ret;
/* Initialize the V4L2 subdev. */
subdev = &entity->subdev;
v4l2_subdev_init(subdev, ops);
subdev->internal_ops = &vsp1_entity_internal_ops;
subdev->entity.function = function;
subdev->entity.ops = &vsp1->media_ops;
subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
if (ops->core == &vsp1_entity_core_ops)
subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
snprintf(subdev->name, sizeof(subdev->name), "%s %s",
dev_name(vsp1->dev), name);
vsp1_entity_init_state(subdev, NULL);
/*
* Allocate the subdev state to store formats and selection
* rectangles.
*/
/*
* FIXME: Drop this call, drivers are not supposed to use
* __v4l2_subdev_state_alloc().
*/
entity->state = __v4l2_subdev_state_alloc(&entity->subdev,
"vsp1:state->lock", &key);
if (IS_ERR(entity->state)) {
media_entity_cleanup(&entity->subdev.entity);
return PTR_ERR(entity->state);
}
return 0;
}
void vsp1_entity_destroy(struct vsp1_entity *entity)
{
if (entity->ops && entity->ops->destroy)
entity->ops->destroy(entity);
if (entity->subdev.ctrl_handler)
v4l2_ctrl_handler_free(entity->subdev.ctrl_handler);
__v4l2_subdev_state_free(entity->state);
media_entity_cleanup(&entity->subdev.entity);
}
@@ -0,0 +1,200 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_entity.h -- R-Car VSP1 Base Entity
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_ENTITY_H__
#define __VSP1_ENTITY_H__
#include <linux/list.h>
#include <linux/mutex.h>
#include <media/v4l2-subdev.h>
struct vsp1_device;
struct vsp1_dl_body;
struct vsp1_dl_list;
struct vsp1_pipeline;
struct vsp1_partition;
enum vsp1_entity_type {
VSP1_ENTITY_BRS,
VSP1_ENTITY_BRU,
VSP1_ENTITY_CLU,
VSP1_ENTITY_HGO,
VSP1_ENTITY_HGT,
VSP1_ENTITY_HSI,
VSP1_ENTITY_HST,
VSP1_ENTITY_IIF,
VSP1_ENTITY_LIF,
VSP1_ENTITY_LUT,
VSP1_ENTITY_RPF,
VSP1_ENTITY_SRU,
VSP1_ENTITY_UDS,
VSP1_ENTITY_UIF,
VSP1_ENTITY_WPF,
};
#define VSP1_ENTITY_MAX_INPUTS 5 /* For the BRU */
/*
* struct vsp1_route - Entity routing configuration
* @type: Entity type this routing entry is associated with
* @index: Entity index this routing entry is associated with
* @reg: Output routing configuration register
* @inputs: Target node value for each input
* @output: Target node value for entity output
*
* Each $vsp1_route entry describes routing configuration for the entity
* specified by the entry's @type and @index. @reg indicates the register that
* holds output routing configuration for the entity, and the @inputs array
* store the target node value for each input of the entity. The @output field
* stores the target node value of the entity output when used as a source for
* histogram generation.
*/
struct vsp1_route {
enum vsp1_entity_type type;
unsigned int index;
unsigned int reg;
unsigned int inputs[VSP1_ENTITY_MAX_INPUTS];
unsigned int output;
};
/**
* struct vsp1_entity_operations - Entity operations
* @destroy: Destroy the entity.
* @configure_stream: Setup the hardware parameters for the stream which do
* not vary between frames (pipeline, formats). Note that
* the vsp1_dl_list argument is only valid for display
* pipeline and will be NULL for mem-to-mem pipelines.
* @configure_frame: Configure the runtime parameters for each frame.
* @configure_partition: Configure partition specific parameters.
* @max_width: Return the max supported width of data that the entity can
* process in a single operation.
* @partition: Process the partition construction based on this entity's
* configuration.
*/
struct vsp1_entity_operations {
void (*destroy)(struct vsp1_entity *entity);
void (*configure_stream)(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
void (*configure_frame)(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
void (*configure_partition)(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
unsigned int (*max_width)(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe);
void (*partition)(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int index,
struct v4l2_rect *window);
};
struct vsp1_entity {
struct vsp1_device *vsp1;
const struct vsp1_entity_operations *ops;
enum vsp1_entity_type type;
unsigned int index;
const struct vsp1_route *route;
const u32 *codes;
unsigned int num_codes;
unsigned int min_width;
unsigned int min_height;
unsigned int max_width;
unsigned int max_height;
struct vsp1_pipeline *pipe;
struct list_head list_dev;
struct list_head list_pipe;
struct media_pad *pads;
unsigned int source_pad;
struct vsp1_entity **sources;
struct vsp1_entity *sink;
unsigned int sink_pad;
struct v4l2_subdev subdev;
struct v4l2_subdev_state *state;
struct mutex lock; /* Protects the state */
};
static inline struct vsp1_entity *to_vsp1_entity(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_entity, subdev);
}
extern const struct v4l2_subdev_core_ops vsp1_entity_core_ops;
int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity,
const char *name, unsigned int num_pads,
const struct v4l2_subdev_ops *ops, u32 function);
void vsp1_entity_destroy(struct vsp1_entity *entity);
int vsp1_entity_link_setup(struct media_entity *entity,
const struct media_pad *local,
const struct media_pad *remote, u32 flags);
struct v4l2_subdev_state *
vsp1_entity_get_state(struct vsp1_entity *entity,
struct v4l2_subdev_state *sd_state,
enum v4l2_subdev_format_whence which);
void vsp1_entity_route_setup(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_body *dlb);
void vsp1_entity_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
void vsp1_entity_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
void vsp1_entity_configure_partition(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb);
void vsp1_entity_adjust_color_space(struct v4l2_mbus_framefmt *format);
struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad);
int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt);
int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt);
int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code);
int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse);
#endif /* __VSP1_ENTITY_H__ */
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_hgo.c -- R-Car VSP1 Histogram Generator 1D
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-vmalloc.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_hgo.h"
#define HGO_DATA_SIZE ((2 + 256) * 4)
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline u32 vsp1_hgo_read(struct vsp1_hgo *hgo, u32 reg)
{
return vsp1_read(hgo->histo.entity.vsp1, reg);
}
static inline void vsp1_hgo_write(struct vsp1_hgo *hgo,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* Frame End Handler
*/
void vsp1_hgo_frame_end(struct vsp1_entity *entity)
{
struct vsp1_hgo *hgo = to_hgo(&entity->subdev);
struct vsp1_histogram_buffer *buf;
unsigned int i;
size_t size;
u32 *data;
buf = vsp1_histogram_buffer_get(&hgo->histo);
if (!buf)
return;
data = buf->addr;
if (hgo->num_bins == 256) {
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM);
for (i = 0; i < 256; ++i) {
vsp1_write(hgo->histo.entity.vsp1,
VI6_HGO_EXT_HIST_ADDR, i);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_EXT_HIST_DATA);
}
size = (2 + 256) * sizeof(u32);
} else if (hgo->max_rgb) {
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM);
for (i = 0; i < 64; ++i)
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i));
size = (2 + 64) * sizeof(u32);
} else {
*data++ = vsp1_hgo_read(hgo, VI6_HGO_R_MAXMIN);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_B_MAXMIN);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_R_SUM);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM);
*data++ = vsp1_hgo_read(hgo, VI6_HGO_B_SUM);
for (i = 0; i < 64; ++i) {
data[i] = vsp1_hgo_read(hgo, VI6_HGO_R_HISTO(i));
data[i+64] = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i));
data[i+128] = vsp1_hgo_read(hgo, VI6_HGO_B_HISTO(i));
}
size = (6 + 64 * 3) * sizeof(u32);
}
vsp1_histogram_buffer_complete(&hgo->histo, buf, size);
}
/* -----------------------------------------------------------------------------
* Controls
*/
#define V4L2_CID_VSP1_HGO_MAX_RGB (V4L2_CID_USER_BASE | 0x1001)
#define V4L2_CID_VSP1_HGO_NUM_BINS (V4L2_CID_USER_BASE | 0x1002)
static const struct v4l2_ctrl_config hgo_max_rgb_control = {
.id = V4L2_CID_VSP1_HGO_MAX_RGB,
.name = "Maximum RGB Mode",
.type = V4L2_CTRL_TYPE_BOOLEAN,
.min = 0,
.max = 1,
.def = 0,
.step = 1,
.flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT,
};
static const s64 hgo_num_bins[] = {
64, 256,
};
static const struct v4l2_ctrl_config hgo_num_bins_control = {
.id = V4L2_CID_VSP1_HGO_NUM_BINS,
.name = "Number of Bins",
.type = V4L2_CTRL_TYPE_INTEGER_MENU,
.min = 0,
.max = 1,
.def = 0,
.qmenu_int = hgo_num_bins,
.flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void hgo_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_hgo *hgo = to_hgo(&entity->subdev);
struct v4l2_rect *compose;
struct v4l2_rect *crop;
unsigned int hratio;
unsigned int vratio;
crop = v4l2_subdev_state_get_crop(state, HISTO_PAD_SINK);
compose = v4l2_subdev_state_get_compose(state, HISTO_PAD_SINK);
vsp1_hgo_write(hgo, dlb, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA);
vsp1_hgo_write(hgo, dlb, VI6_HGO_OFFSET,
(crop->left << VI6_HGO_OFFSET_HOFFSET_SHIFT) |
(crop->top << VI6_HGO_OFFSET_VOFFSET_SHIFT));
vsp1_hgo_write(hgo, dlb, VI6_HGO_SIZE,
(crop->width << VI6_HGO_SIZE_HSIZE_SHIFT) |
(crop->height << VI6_HGO_SIZE_VSIZE_SHIFT));
scoped_guard(mutex, hgo->ctrls.handler.lock) {
hgo->max_rgb = hgo->ctrls.max_rgb->cur.val;
if (hgo->ctrls.num_bins)
hgo->num_bins = hgo_num_bins[hgo->ctrls.num_bins->cur.val];
}
hratio = crop->width * 2 / compose->width / 3;
vratio = crop->height * 2 / compose->height / 3;
vsp1_hgo_write(hgo, dlb, VI6_HGO_MODE,
(hgo->num_bins == 256 ? VI6_HGO_MODE_STEP : 0) |
(hgo->max_rgb ? VI6_HGO_MODE_MAXRGB : 0) |
(hratio << VI6_HGO_MODE_HRATIO_SHIFT) |
(vratio << VI6_HGO_MODE_VRATIO_SHIFT));
}
static const struct vsp1_entity_operations hgo_entity_ops = {
.configure_stream = hgo_configure_stream,
.destroy = vsp1_histogram_destroy,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
static const unsigned int hgo_mbus_formats[] = {
MEDIA_BUS_FMT_AYUV8_1X32,
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
};
struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1)
{
struct vsp1_hgo *hgo;
int ret;
hgo = devm_kzalloc(vsp1->dev, sizeof(*hgo), GFP_KERNEL);
if (hgo == NULL)
return ERR_PTR(-ENOMEM);
/* Initialize the video device and queue for statistics data. */
ret = vsp1_histogram_init(vsp1, &hgo->histo, VSP1_ENTITY_HGO, "hgo",
&hgo_entity_ops, hgo_mbus_formats,
ARRAY_SIZE(hgo_mbus_formats),
HGO_DATA_SIZE, V4L2_META_FMT_VSP1_HGO);
if (ret < 0) {
vsp1_entity_destroy(&hgo->histo.entity);
return ERR_PTR(ret);
}
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&hgo->ctrls.handler,
vsp1->info->gen >= 3 ? 2 : 1);
hgo->ctrls.max_rgb = v4l2_ctrl_new_custom(&hgo->ctrls.handler,
&hgo_max_rgb_control, NULL);
if (vsp1->info->gen >= 3)
hgo->ctrls.num_bins =
v4l2_ctrl_new_custom(&hgo->ctrls.handler,
&hgo_num_bins_control, NULL);
hgo->max_rgb = false;
hgo->num_bins = 64;
hgo->histo.entity.subdev.ctrl_handler = &hgo->ctrls.handler;
return hgo;
}
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_hgo.h -- R-Car VSP1 Histogram Generator 1D
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_HGO_H__
#define __VSP1_HGO_H__
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_histo.h"
struct vsp1_device;
struct vsp1_hgo {
struct vsp1_histogram histo;
struct {
struct v4l2_ctrl_handler handler;
struct v4l2_ctrl *max_rgb;
struct v4l2_ctrl *num_bins;
} ctrls;
bool max_rgb;
unsigned int num_bins;
};
static inline struct vsp1_hgo *to_hgo(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_hgo, histo.entity.subdev);
}
struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1);
void vsp1_hgo_frame_end(struct vsp1_entity *hgo);
#endif /* __VSP1_HGO_H__ */
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_hgt.c -- R-Car VSP1 Histogram Generator 2D
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-vmalloc.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_hgt.h"
#define HGT_DATA_SIZE ((2 + 6 * 32) * 4)
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline u32 vsp1_hgt_read(struct vsp1_hgt *hgt, u32 reg)
{
return vsp1_read(hgt->histo.entity.vsp1, reg);
}
static inline void vsp1_hgt_write(struct vsp1_hgt *hgt,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* Frame End Handler
*/
void vsp1_hgt_frame_end(struct vsp1_entity *entity)
{
struct vsp1_hgt *hgt = to_hgt(&entity->subdev);
struct vsp1_histogram_buffer *buf;
unsigned int m;
unsigned int n;
u32 *data;
buf = vsp1_histogram_buffer_get(&hgt->histo);
if (!buf)
return;
data = buf->addr;
*data++ = vsp1_hgt_read(hgt, VI6_HGT_MAXMIN);
*data++ = vsp1_hgt_read(hgt, VI6_HGT_SUM);
for (m = 0; m < 6; ++m)
for (n = 0; n < 32; ++n)
*data++ = vsp1_hgt_read(hgt, VI6_HGT_HISTO(m, n));
vsp1_histogram_buffer_complete(&hgt->histo, buf, HGT_DATA_SIZE);
}
/* -----------------------------------------------------------------------------
* Controls
*/
#define V4L2_CID_VSP1_HGT_HUE_AREAS (V4L2_CID_USER_BASE | 0x1001)
static int hgt_hue_areas_try_ctrl(struct v4l2_ctrl *ctrl)
{
const u8 *values = ctrl->p_new.p_u8;
unsigned int i;
/*
* The hardware has constraints on the hue area boundaries beyond the
* control min, max and step. The values must match one of the following
* expressions.
*
* 0L <= 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U
* 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U <= 0L
*
* Start by verifying the common part...
*/
for (i = 1; i < (HGT_NUM_HUE_AREAS * 2) - 1; ++i) {
if (values[i] > values[i+1])
return -EINVAL;
}
/* ... and handle 0L separately. */
if (values[0] > values[1] && values[11] > values[0])
return -EINVAL;
return 0;
}
static int hgt_hue_areas_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_hgt *hgt = container_of(ctrl->handler, struct vsp1_hgt,
ctrls);
memcpy(hgt->hue_areas, ctrl->p_new.p_u8, sizeof(hgt->hue_areas));
return 0;
}
static const struct v4l2_ctrl_ops hgt_hue_areas_ctrl_ops = {
.try_ctrl = hgt_hue_areas_try_ctrl,
.s_ctrl = hgt_hue_areas_s_ctrl,
};
static const struct v4l2_ctrl_config hgt_hue_areas = {
.ops = &hgt_hue_areas_ctrl_ops,
.id = V4L2_CID_VSP1_HGT_HUE_AREAS,
.name = "Boundary Values for Hue Area",
.type = V4L2_CTRL_TYPE_U8,
.min = 0,
.max = 255,
.def = 0,
.step = 1,
.dims = { 12 },
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void hgt_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_hgt *hgt = to_hgt(&entity->subdev);
struct v4l2_rect *compose;
struct v4l2_rect *crop;
unsigned int hratio;
unsigned int vratio;
u8 lower;
u8 upper;
unsigned int i;
crop = v4l2_subdev_state_get_crop(state, HISTO_PAD_SINK);
compose = v4l2_subdev_state_get_compose(state, HISTO_PAD_SINK);
vsp1_hgt_write(hgt, dlb, VI6_HGT_REGRST, VI6_HGT_REGRST_RCLEA);
vsp1_hgt_write(hgt, dlb, VI6_HGT_OFFSET,
(crop->left << VI6_HGT_OFFSET_HOFFSET_SHIFT) |
(crop->top << VI6_HGT_OFFSET_VOFFSET_SHIFT));
vsp1_hgt_write(hgt, dlb, VI6_HGT_SIZE,
(crop->width << VI6_HGT_SIZE_HSIZE_SHIFT) |
(crop->height << VI6_HGT_SIZE_VSIZE_SHIFT));
scoped_guard(mutex, hgt->ctrls.lock) {
for (i = 0; i < HGT_NUM_HUE_AREAS; ++i) {
lower = hgt->hue_areas[i*2 + 0];
upper = hgt->hue_areas[i*2 + 1];
vsp1_hgt_write(hgt, dlb, VI6_HGT_HUE_AREA(i),
(lower << VI6_HGT_HUE_AREA_LOWER_SHIFT) |
(upper << VI6_HGT_HUE_AREA_UPPER_SHIFT));
}
}
hratio = crop->width * 2 / compose->width / 3;
vratio = crop->height * 2 / compose->height / 3;
vsp1_hgt_write(hgt, dlb, VI6_HGT_MODE,
(hratio << VI6_HGT_MODE_HRATIO_SHIFT) |
(vratio << VI6_HGT_MODE_VRATIO_SHIFT));
}
static const struct vsp1_entity_operations hgt_entity_ops = {
.configure_stream = hgt_configure_stream,
.destroy = vsp1_histogram_destroy,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
static const unsigned int hgt_mbus_formats[] = {
MEDIA_BUS_FMT_AHSV8888_1X32,
};
struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1)
{
struct vsp1_hgt *hgt;
int ret;
hgt = devm_kzalloc(vsp1->dev, sizeof(*hgt), GFP_KERNEL);
if (hgt == NULL)
return ERR_PTR(-ENOMEM);
/* Initialize the video device and queue for statistics data. */
ret = vsp1_histogram_init(vsp1, &hgt->histo, VSP1_ENTITY_HGT, "hgt",
&hgt_entity_ops, hgt_mbus_formats,
ARRAY_SIZE(hgt_mbus_formats),
HGT_DATA_SIZE, V4L2_META_FMT_VSP1_HGT);
if (ret < 0) {
vsp1_entity_destroy(&hgt->histo.entity);
return ERR_PTR(ret);
}
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&hgt->ctrls, 1);
v4l2_ctrl_new_custom(&hgt->ctrls, &hgt_hue_areas, NULL);
hgt->histo.entity.subdev.ctrl_handler = &hgt->ctrls;
v4l2_ctrl_handler_setup(&hgt->ctrls);
return hgt;
}
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_hgt.h -- R-Car VSP1 Histogram Generator 2D
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se)
*/
#ifndef __VSP1_HGT_H__
#define __VSP1_HGT_H__
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_histo.h"
struct vsp1_device;
#define HGT_NUM_HUE_AREAS 6
struct vsp1_hgt {
struct vsp1_histogram histo;
struct v4l2_ctrl_handler ctrls;
u8 hue_areas[HGT_NUM_HUE_AREAS * 2];
};
static inline struct vsp1_hgt *to_hgt(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_hgt, histo.entity.subdev);
}
struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1);
void vsp1_hgt_frame_end(struct vsp1_entity *hgt);
#endif /* __VSP1_HGT_H__ */
@@ -0,0 +1,591 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_histo.c -- R-Car VSP1 Histogram API
*
* Copyright (C) 2016 Renesas Electronics Corporation
* Copyright (C) 2016 Laurent Pinchart
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-vmalloc.h>
#include "vsp1.h"
#include "vsp1_histo.h"
#include "vsp1_pipe.h"
#define HISTO_MIN_SIZE 4U
#define HISTO_MAX_SIZE 8192U
/* -----------------------------------------------------------------------------
* Buffer Operations
*/
static inline struct vsp1_histogram_buffer *
to_vsp1_histogram_buffer(struct vb2_v4l2_buffer *vbuf)
{
return container_of(vbuf, struct vsp1_histogram_buffer, buf);
}
struct vsp1_histogram_buffer *
vsp1_histogram_buffer_get(struct vsp1_histogram *histo)
{
struct vsp1_histogram_buffer *buf;
guard(spinlock)(&histo->irqlock);
if (list_empty(&histo->irqqueue))
return NULL;
buf = list_first_entry(&histo->irqqueue, struct vsp1_histogram_buffer,
queue);
list_del(&buf->queue);
histo->readout = true;
return buf;
}
void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo,
struct vsp1_histogram_buffer *buf,
size_t size)
{
struct vsp1_pipeline *pipe = histo->entity.pipe;
/*
* The pipeline pointer is guaranteed to be valid as this function is
* called from the frame completion interrupt handler, which can only
* occur when video streaming is active.
*/
buf->buf.sequence = pipe->sequence;
buf->buf.vb2_buf.timestamp = ktime_get_ns();
vb2_set_plane_payload(&buf->buf.vb2_buf, 0, size);
vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE);
guard(spinlock)(&histo->irqlock);
histo->readout = false;
wake_up(&histo->wait_queue);
}
/* -----------------------------------------------------------------------------
* videobuf2 Queue Operations
*/
static int histo_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct vsp1_histogram *histo = vb2_get_drv_priv(vq);
if (*nplanes) {
if (*nplanes != 1)
return -EINVAL;
if (sizes[0] < histo->data_size)
return -EINVAL;
return 0;
}
*nplanes = 1;
sizes[0] = histo->data_size;
return 0;
}
static int histo_buffer_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue);
struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf);
if (vb->num_planes != 1)
return -EINVAL;
if (vb2_plane_size(vb, 0) < histo->data_size)
return -EINVAL;
buf->addr = vb2_plane_vaddr(vb, 0);
return 0;
}
static void histo_buffer_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue);
struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf);
guard(spinlock_irq)(&histo->irqlock);
list_add_tail(&buf->queue, &histo->irqqueue);
}
static int histo_start_streaming(struct vb2_queue *vq, unsigned int count)
{
return 0;
}
static void histo_stop_streaming(struct vb2_queue *vq)
{
struct vsp1_histogram *histo = vb2_get_drv_priv(vq);
struct vsp1_histogram_buffer *buffer;
guard(spinlock_irq)(&histo->irqlock);
/* Remove all buffers from the IRQ queue. */
list_for_each_entry(buffer, &histo->irqqueue, queue)
vb2_buffer_done(&buffer->buf.vb2_buf, VB2_BUF_STATE_ERROR);
INIT_LIST_HEAD(&histo->irqqueue);
/* Wait for the buffer being read out (if any) to complete. */
wait_event_lock_irq(histo->wait_queue, !histo->readout, histo->irqlock);
}
static const struct vb2_ops histo_video_queue_qops = {
.queue_setup = histo_queue_setup,
.buf_prepare = histo_buffer_prepare,
.buf_queue = histo_buffer_queue,
.start_streaming = histo_start_streaming,
.stop_streaming = histo_stop_streaming,
};
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static int histo_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->pad == HISTO_PAD_SOURCE) {
if (code->index > 0)
return -EINVAL;
code->code = MEDIA_BUS_FMT_METADATA_FIXED;
return 0;
}
return vsp1_subdev_enum_mbus_code(subdev, sd_state, code);
}
static int histo_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
if (fse->pad != HISTO_PAD_SINK)
return -ENOTTY;
return vsp1_subdev_enum_frame_size(subdev, sd_state, fse);
}
static int histo_get_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_histogram *histo = subdev_to_histo(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *crop;
if (sel->pad != HISTO_PAD_SINK)
return -EINVAL;
guard(mutex)(&histo->entity.lock);
state = vsp1_entity_get_state(&histo->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
case V4L2_SEL_TGT_COMPOSE_DEFAULT:
crop = v4l2_subdev_state_get_crop(state, HISTO_PAD_SINK);
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = crop->width;
sel->r.height = crop->height;
break;
case V4L2_SEL_TGT_CROP_BOUNDS:
case V4L2_SEL_TGT_CROP_DEFAULT:
format = v4l2_subdev_state_get_format(state, HISTO_PAD_SINK);
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = format->width;
sel->r.height = format->height;
break;
case V4L2_SEL_TGT_COMPOSE:
sel->r = *v4l2_subdev_state_get_compose(state, sel->pad);
break;
case V4L2_SEL_TGT_CROP:
sel->r = *v4l2_subdev_state_get_crop(state, sel->pad);
break;
default:
return -EINVAL;
}
return 0;
}
static int histo_set_crop(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_mbus_framefmt *format;
/* The crop rectangle must be inside the input frame. */
format = v4l2_subdev_state_get_format(sd_state, HISTO_PAD_SINK);
sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1);
sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1);
sel->r.width = clamp_t(unsigned int, sel->r.width, HISTO_MIN_SIZE,
format->width - sel->r.left);
sel->r.height = clamp_t(unsigned int, sel->r.height, HISTO_MIN_SIZE,
format->height - sel->r.top);
/* Set the crop rectangle and reset the compose rectangle. */
*v4l2_subdev_state_get_crop(sd_state, sel->pad) = sel->r;
*v4l2_subdev_state_get_compose(sd_state, sel->pad) = sel->r;
return 0;
}
static int histo_set_compose(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct v4l2_rect *compose;
struct v4l2_rect *crop;
unsigned int ratio;
/*
* The compose rectangle is used to configure downscaling, the top left
* corner is fixed to (0,0) and the size to 1/2 or 1/4 of the crop
* rectangle.
*/
sel->r.left = 0;
sel->r.top = 0;
crop = v4l2_subdev_state_get_crop(sd_state, sel->pad);
/*
* Clamp the width and height to acceptable values first and then
* compute the closest rounded dividing ratio.
*
* Ratio Rounded ratio
* --------------------------
* [1.0 1.5[ 1
* [1.5 3.0[ 2
* [3.0 4.0] 4
*
* The rounded ratio can be computed using
*
* 1 << (ceil(ratio * 2) / 3)
*/
sel->r.width = clamp(sel->r.width, crop->width / 4, crop->width);
ratio = 1 << (crop->width * 2 / sel->r.width / 3);
sel->r.width = crop->width / ratio;
sel->r.height = clamp(sel->r.height, crop->height / 4, crop->height);
ratio = 1 << (crop->height * 2 / sel->r.height / 3);
sel->r.height = crop->height / ratio;
compose = v4l2_subdev_state_get_compose(sd_state, sel->pad);
*compose = sel->r;
return 0;
}
static int histo_set_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_histogram *histo = subdev_to_histo(subdev);
struct v4l2_subdev_state *state;
if (sel->pad != HISTO_PAD_SINK)
return -EINVAL;
guard(mutex)(&histo->entity.lock);
state = vsp1_entity_get_state(&histo->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
if (sel->target == V4L2_SEL_TGT_CROP)
return histo_set_crop(subdev, state, sel);
else if (sel->target == V4L2_SEL_TGT_COMPOSE)
return histo_set_compose(subdev, state, sel);
else
return -EINVAL;
}
static int histo_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *selection;
unsigned int i;
state = vsp1_entity_get_state(entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, fmt->pad);
guard(mutex)(&entity->lock);
if (fmt->pad == HISTO_PAD_SINK) {
/*
* Default to the first media bus code if the requested format
* is not supported.
*/
for (i = 0; i < entity->num_codes; ++i) {
if (fmt->format.code == entity->codes[i])
break;
}
format->code = i < entity->num_codes
? entity->codes[i] : entity->codes[0];
format->width = clamp_t(unsigned int, fmt->format.width,
entity->min_width, entity->max_width);
format->height = clamp_t(unsigned int, fmt->format.height,
entity->min_height, entity->max_height);
format->field = V4L2_FIELD_NONE;
format->colorspace = fmt->format.colorspace;
format->xfer_func = fmt->format.xfer_func;
format->ycbcr_enc = fmt->format.ycbcr_enc;
format->quantization = fmt->format.quantization;
vsp1_entity_adjust_color_space(format);
/* Reset the crop and compose rectangles. */
selection = v4l2_subdev_state_get_crop(state, fmt->pad);
selection->left = 0;
selection->top = 0;
selection->width = format->width;
selection->height = format->height;
selection = v4l2_subdev_state_get_compose(state, fmt->pad);
selection->left = 0;
selection->top = 0;
selection->width = format->width;
selection->height = format->height;
} else {
format->code = MEDIA_BUS_FMT_METADATA_FIXED;
format->width = 0;
format->height = 0;
format->field = V4L2_FIELD_NONE;
format->colorspace = V4L2_COLORSPACE_RAW;
}
fmt->format = *format;
return 0;
}
static const struct v4l2_subdev_pad_ops histo_pad_ops = {
.enum_mbus_code = histo_enum_mbus_code,
.enum_frame_size = histo_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = histo_set_format,
.get_selection = histo_get_selection,
.set_selection = histo_set_selection,
};
static const struct v4l2_subdev_ops histo_ops = {
.core = &vsp1_entity_core_ops,
.pad = &histo_pad_ops,
};
/* -----------------------------------------------------------------------------
* V4L2 ioctls
*/
static int histo_v4l2_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct v4l2_fh *vfh = file_to_v4l2_fh(file);
struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev);
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
| V4L2_CAP_VIDEO_CAPTURE_MPLANE
| V4L2_CAP_VIDEO_OUTPUT_MPLANE
| V4L2_CAP_META_CAPTURE;
strscpy(cap->driver, "vsp1", sizeof(cap->driver));
strscpy(cap->card, histo->video.name, sizeof(cap->card));
return 0;
}
static int histo_v4l2_enum_format(struct file *file, void *fh,
struct v4l2_fmtdesc *f)
{
struct v4l2_fh *vfh = file_to_v4l2_fh(file);
struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev);
if (f->index > 0 || f->type != histo->queue.type)
return -EINVAL;
f->pixelformat = histo->meta_format;
return 0;
}
static int histo_v4l2_get_format(struct file *file, void *fh,
struct v4l2_format *format)
{
struct v4l2_fh *vfh = file_to_v4l2_fh(file);
struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev);
struct v4l2_meta_format *meta = &format->fmt.meta;
if (format->type != histo->queue.type)
return -EINVAL;
memset(meta, 0, sizeof(*meta));
meta->dataformat = histo->meta_format;
meta->buffersize = histo->data_size;
return 0;
}
static const struct v4l2_ioctl_ops histo_v4l2_ioctl_ops = {
.vidioc_querycap = histo_v4l2_querycap,
.vidioc_enum_fmt_meta_cap = histo_v4l2_enum_format,
.vidioc_g_fmt_meta_cap = histo_v4l2_get_format,
.vidioc_s_fmt_meta_cap = histo_v4l2_get_format,
.vidioc_try_fmt_meta_cap = histo_v4l2_get_format,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
/* -----------------------------------------------------------------------------
* V4L2 File Operations
*/
static const struct v4l2_file_operations histo_v4l2_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
};
static void vsp1_histogram_cleanup(struct vsp1_histogram *histo)
{
if (video_is_registered(&histo->video))
video_unregister_device(&histo->video);
media_entity_cleanup(&histo->video.entity);
}
void vsp1_histogram_destroy(struct vsp1_entity *entity)
{
struct vsp1_histogram *histo = subdev_to_histo(&entity->subdev);
vsp1_histogram_cleanup(histo);
}
int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo,
enum vsp1_entity_type type, const char *name,
const struct vsp1_entity_operations *ops,
const unsigned int *formats, unsigned int num_formats,
size_t data_size, u32 meta_format)
{
int ret;
histo->data_size = data_size;
histo->meta_format = meta_format;
histo->pad.flags = MEDIA_PAD_FL_SINK;
histo->video.vfl_dir = VFL_DIR_RX;
mutex_init(&histo->lock);
spin_lock_init(&histo->irqlock);
INIT_LIST_HEAD(&histo->irqqueue);
init_waitqueue_head(&histo->wait_queue);
/* Initialize the VSP entity... */
histo->entity.ops = ops;
histo->entity.type = type;
histo->entity.codes = formats;
histo->entity.num_codes = num_formats;
histo->entity.min_width = HISTO_MIN_SIZE;
histo->entity.min_height = HISTO_MIN_SIZE;
histo->entity.max_width = HISTO_MAX_SIZE;
histo->entity.max_height = HISTO_MAX_SIZE;
ret = vsp1_entity_init(vsp1, &histo->entity, name, 2, &histo_ops,
MEDIA_ENT_F_PROC_VIDEO_STATISTICS);
if (ret < 0)
return ret;
/* ... and the media entity... */
ret = media_entity_pads_init(&histo->video.entity, 1, &histo->pad);
if (ret < 0)
return ret;
/* ... and the video node... */
histo->video.v4l2_dev = &vsp1->v4l2_dev;
histo->video.fops = &histo_v4l2_fops;
snprintf(histo->video.name, sizeof(histo->video.name),
"%s histo", histo->entity.subdev.name);
histo->video.vfl_type = VFL_TYPE_VIDEO;
histo->video.release = video_device_release_empty;
histo->video.ioctl_ops = &histo_v4l2_ioctl_ops;
histo->video.device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
video_set_drvdata(&histo->video, histo);
/* ... and the buffers queue... */
histo->queue.type = V4L2_BUF_TYPE_META_CAPTURE;
histo->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
histo->queue.lock = &histo->lock;
histo->queue.drv_priv = histo;
histo->queue.buf_struct_size = sizeof(struct vsp1_histogram_buffer);
histo->queue.ops = &histo_video_queue_qops;
histo->queue.mem_ops = &vb2_vmalloc_memops;
histo->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
histo->queue.dev = vsp1->dev;
ret = vb2_queue_init(&histo->queue);
if (ret < 0) {
dev_err(vsp1->dev, "failed to initialize vb2 queue\n");
goto error;
}
/* ... and register the video device. */
histo->video.queue = &histo->queue;
ret = video_register_device(&histo->video, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
dev_err(vsp1->dev, "failed to register video device\n");
goto error;
}
return 0;
error:
vsp1_histogram_cleanup(histo);
return ret;
}
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_histo.h -- R-Car VSP1 Histogram API
*
* Copyright (C) 2016 Renesas Electronics Corporation
* Copyright (C) 2016 Laurent Pinchart
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_HISTO_H__
#define __VSP1_HISTO_H__
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-dev.h>
#include <media/videobuf2-v4l2.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define HISTO_PAD_SINK 0
#define HISTO_PAD_SOURCE 1
struct vsp1_histogram_buffer {
struct vb2_v4l2_buffer buf;
struct list_head queue;
void *addr;
};
struct vsp1_histogram {
struct vsp1_entity entity;
struct video_device video;
struct media_pad pad;
size_t data_size;
u32 meta_format;
struct mutex lock;
struct vb2_queue queue;
spinlock_t irqlock;
struct list_head irqqueue;
wait_queue_head_t wait_queue;
bool readout;
};
static inline struct vsp1_histogram *vdev_to_histo(struct video_device *vdev)
{
return container_of(vdev, struct vsp1_histogram, video);
}
static inline struct vsp1_histogram *subdev_to_histo(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_histogram, entity.subdev);
}
int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo,
enum vsp1_entity_type type, const char *name,
const struct vsp1_entity_operations *ops,
const unsigned int *formats, unsigned int num_formats,
size_t data_size, u32 meta_format);
void vsp1_histogram_destroy(struct vsp1_entity *entity);
struct vsp1_histogram_buffer *
vsp1_histogram_buffer_get(struct vsp1_histogram *histo);
void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo,
struct vsp1_histogram_buffer *buf,
size_t size);
#endif /* __VSP1_HISTO_H__ */
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_hsit.c -- R-Car VSP1 Hue Saturation value (Inverse) Transform
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/mutex.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_hsit.h"
#define HSIT_MIN_SIZE 4U
#define HSIT_MAX_SIZE 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_hsit_write(struct vsp1_hsit *hsit,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int hsit_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
};
static int hsit_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct vsp1_hsit *hsit = to_hsit(subdev);
if (code->index > 0)
return -EINVAL;
if ((code->pad == HSIT_PAD_SINK && !hsit->inverse) |
(code->pad == HSIT_PAD_SOURCE && hsit->inverse))
code->code = MEDIA_BUS_FMT_ARGB8888_1X32;
else
code->code = MEDIA_BUS_FMT_AHSV8888_1X32;
return 0;
}
static int hsit_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct vsp1_hsit *hsit = to_hsit(subdev);
u32 code;
if (fse->index)
return -EINVAL;
if ((fse->pad == HSIT_PAD_SINK && !hsit->inverse) |
(fse->pad == HSIT_PAD_SOURCE && hsit->inverse))
code = MEDIA_BUS_FMT_ARGB8888_1X32;
else
code = MEDIA_BUS_FMT_AHSV8888_1X32;
if (fse->code != code)
return -EINVAL;
if (fse->pad == 0) {
fse->min_width = entity->min_width;
fse->max_width = entity->max_width;
fse->min_height = entity->min_height;
fse->max_height = entity->max_height;
} else {
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
state = vsp1_entity_get_state(entity, sd_state, fse->which);
if (!state)
return -EINVAL;
/*
* The size on the source pad is fixed and always identical to
* the sink pad.
*/
format = v4l2_subdev_state_get_format(state, HSIT_PAD_SINK);
guard(mutex)(&entity->lock);
fse->min_width = format->width;
fse->max_width = format->width;
fse->min_height = format->height;
fse->max_height = format->height;
}
return 0;
}
static int hsit_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_hsit *hsit = to_hsit(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
guard(mutex)(&hsit->entity.lock);
state = vsp1_entity_get_state(&hsit->entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, fmt->pad);
if (fmt->pad == HSIT_PAD_SOURCE) {
/*
* The HST and HSI output format code and resolution can't be
* modified.
*/
fmt->format = *format;
return 0;
}
format->code = hsit->inverse ? MEDIA_BUS_FMT_AHSV8888_1X32
: MEDIA_BUS_FMT_ARGB8888_1X32;
format->width = clamp_t(unsigned int, fmt->format.width,
HSIT_MIN_SIZE, HSIT_MAX_SIZE);
format->height = clamp_t(unsigned int, fmt->format.height,
HSIT_MIN_SIZE, HSIT_MAX_SIZE);
format->field = V4L2_FIELD_NONE;
format->colorspace = fmt->format.colorspace;
format->xfer_func = fmt->format.xfer_func;
format->ycbcr_enc = fmt->format.ycbcr_enc;
format->quantization = fmt->format.quantization;
vsp1_entity_adjust_color_space(format);
fmt->format = *format;
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, HSIT_PAD_SOURCE);
*format = fmt->format;
format->code = hsit->inverse ? MEDIA_BUS_FMT_ARGB8888_1X32
: MEDIA_BUS_FMT_AHSV8888_1X32;
vsp1_entity_adjust_color_space(format);
return 0;
}
static const struct v4l2_subdev_pad_ops hsit_pad_ops = {
.enum_mbus_code = hsit_enum_mbus_code,
.enum_frame_size = hsit_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = hsit_set_format,
};
static const struct v4l2_subdev_ops hsit_ops = {
.pad = &hsit_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void hsit_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_hsit *hsit = to_hsit(&entity->subdev);
if (hsit->inverse)
vsp1_hsit_write(hsit, dlb, VI6_HSI_CTRL, VI6_HSI_CTRL_EN);
else
vsp1_hsit_write(hsit, dlb, VI6_HST_CTRL, VI6_HST_CTRL_EN);
}
static const struct vsp1_entity_operations hsit_entity_ops = {
.configure_stream = hsit_configure_stream,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse)
{
struct vsp1_hsit *hsit;
int ret;
hsit = devm_kzalloc(vsp1->dev, sizeof(*hsit), GFP_KERNEL);
if (hsit == NULL)
return ERR_PTR(-ENOMEM);
hsit->inverse = inverse;
hsit->entity.ops = &hsit_entity_ops;
if (inverse)
hsit->entity.type = VSP1_ENTITY_HSI;
else
hsit->entity.type = VSP1_ENTITY_HST;
hsit->entity.codes = hsit_codes;
hsit->entity.num_codes = ARRAY_SIZE(hsit_codes);
hsit->entity.min_width = HSIT_MIN_SIZE;
hsit->entity.min_height = HSIT_MIN_SIZE;
hsit->entity.max_width = HSIT_MAX_SIZE;
hsit->entity.max_height = HSIT_MAX_SIZE;
ret = vsp1_entity_init(vsp1, &hsit->entity, inverse ? "hsi" : "hst",
2, &hsit_ops,
MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV);
if (ret < 0)
return ERR_PTR(ret);
return hsit;
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_hsit.h -- R-Car VSP1 Hue Saturation value (Inverse) Transform
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_HSIT_H__
#define __VSP1_HSIT_H__
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define HSIT_PAD_SINK 0
#define HSIT_PAD_SOURCE 1
struct vsp1_hsit {
struct vsp1_entity entity;
bool inverse;
};
static inline struct vsp1_hsit *to_hsit(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_hsit, entity.subdev);
}
struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse);
#endif /* __VSP1_HSIT_H__ */
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_iif.c -- R-Car VSP1 IIF (ISP Interface)
*
* Copyright (C) 2025 Ideas On Board Oy
* Copyright (C) 2025 Renesas Corporation
*/
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_iif.h"
#define IIF_MIN_WIDTH 128U
#define IIF_MIN_HEIGHT 32U
#define IIF_MAX_WIDTH 5120U
#define IIF_MAX_HEIGHT 4096U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_iif_write(struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int iif_codes[] = {
MEDIA_BUS_FMT_Y8_1X8,
MEDIA_BUS_FMT_Y10_1X10,
MEDIA_BUS_FMT_Y12_1X12,
MEDIA_BUS_FMT_Y16_1X16,
MEDIA_BUS_FMT_METADATA_FIXED
};
static const struct v4l2_subdev_pad_ops iif_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = vsp1_subdev_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_subdev_set_pad_format,
};
static const struct v4l2_subdev_ops iif_ops = {
.pad = &iif_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void iif_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
vsp1_iif_write(dlb, VI6_IIF_CTRL, VI6_IIF_CTRL_CTRL);
}
static const struct vsp1_entity_operations iif_entity_ops = {
.configure_stream = iif_configure_stream,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_iif *vsp1_iif_create(struct vsp1_device *vsp1)
{
struct vsp1_iif *iif;
int ret;
iif = devm_kzalloc(vsp1->dev, sizeof(*iif), GFP_KERNEL);
if (!iif)
return ERR_PTR(-ENOMEM);
iif->entity.ops = &iif_entity_ops;
iif->entity.type = VSP1_ENTITY_IIF;
iif->entity.codes = iif_codes;
iif->entity.num_codes = ARRAY_SIZE(iif_codes);
iif->entity.min_width = IIF_MIN_WIDTH;
iif->entity.min_height = IIF_MIN_HEIGHT;
iif->entity.max_width = IIF_MAX_WIDTH;
iif->entity.max_height = IIF_MAX_HEIGHT;
/*
* The IIF is never exposed to userspace, but media entity registration
* requires a function to be set. Use PROC_VIDEO_PIXEL_FORMATTER just to
* avoid triggering a WARN_ON(), the value won't be seen anywhere.
*/
ret = vsp1_entity_init(vsp1, &iif->entity, "iif", 3, &iif_ops,
MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER);
if (ret < 0)
return ERR_PTR(ret);
return iif;
}
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_iif.h -- R-Car VSP1 IIF (ISP Interface)
*
* Copyright (C) 2025 Ideas On Board Oy
* Copyright (C) 2025 Renesas Corporation
*/
#ifndef __VSP1_IIF_H__
#define __VSP1_IIF_H__
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
#define VSPX_IIF_SINK_PAD_IMG 0
#define VSPX_IIF_SINK_PAD_CONFIG 2
struct vsp1_iif {
struct vsp1_entity entity;
};
static inline struct vsp1_iif *to_iif(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_iif, entity.subdev);
}
struct vsp1_iif *vsp1_iif_create(struct vsp1_device *vsp1);
#endif /* __VSP1_IIF_H__ */
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_lif.c -- R-Car VSP1 LCD Controller Interface
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_lif.h"
#define LIF_MIN_SIZE 2U
#define LIF_MAX_SIZE 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_lif_write(struct vsp1_lif *lif,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg + lif->entity.index * VI6_LIF_OFFSET,
data);
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int lif_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static const struct v4l2_subdev_pad_ops lif_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = vsp1_subdev_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_subdev_set_pad_format,
};
static const struct v4l2_subdev_ops lif_ops = {
.pad = &lif_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void lif_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
const struct v4l2_mbus_framefmt *format;
struct vsp1_lif *lif = to_lif(&entity->subdev);
unsigned int hbth;
unsigned int obth;
unsigned int lbth;
format = v4l2_subdev_state_get_format(state, LIF_PAD_SOURCE);
switch (entity->vsp1->version & VI6_IP_VERSION_MODEL_MASK) {
case VI6_IP_VERSION_MODEL_VSPD_GEN2:
case VI6_IP_VERSION_MODEL_VSPD_V2H:
hbth = 1536;
obth = min(128U, (format->width + 1) / 2 * format->height - 4);
lbth = 1520;
break;
case VI6_IP_VERSION_MODEL_VSPDL_GEN3:
case VI6_IP_VERSION_MODEL_VSPD_V3:
case VI6_IP_VERSION_MODEL_VSPD_RZG2L:
hbth = 0;
obth = 1500;
lbth = 0;
break;
case VI6_IP_VERSION_MODEL_VSPD_GEN3:
case VI6_IP_VERSION_MODEL_VSPD_GEN4:
default:
hbth = 0;
obth = 3000;
lbth = 0;
break;
}
vsp1_lif_write(lif, dlb, VI6_LIF_CSBTH,
(hbth << VI6_LIF_CSBTH_HBTH_SHIFT) |
(lbth << VI6_LIF_CSBTH_LBTH_SHIFT));
vsp1_lif_write(lif, dlb, VI6_LIF_CTRL,
(obth << VI6_LIF_CTRL_OBTH_SHIFT) |
(format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) |
VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN);
/*
* On R-Car V3M and RZ/G2L the LIF0 buffer attribute register has to be
* set to a non-default value to guarantee proper operation (otherwise
* artifacts may appear on the output). The value required by the
* manual is not explained but is likely a buffer size or threshold.
*/
if (vsp1_feature(entity->vsp1, VSP1_HAS_NON_ZERO_LBA))
vsp1_lif_write(lif, dlb, VI6_LIF_LBA,
VI6_LIF_LBA_LBA0 |
(1536 << VI6_LIF_LBA_LBA1_SHIFT));
}
static const struct vsp1_entity_operations lif_entity_ops = {
.configure_stream = lif_configure_stream,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1, unsigned int index)
{
struct vsp1_lif *lif;
int ret;
lif = devm_kzalloc(vsp1->dev, sizeof(*lif), GFP_KERNEL);
if (lif == NULL)
return ERR_PTR(-ENOMEM);
lif->entity.ops = &lif_entity_ops;
lif->entity.type = VSP1_ENTITY_LIF;
lif->entity.index = index;
lif->entity.codes = lif_codes;
lif->entity.num_codes = ARRAY_SIZE(lif_codes);
lif->entity.min_width = LIF_MIN_SIZE;
lif->entity.min_height = LIF_MIN_SIZE;
lif->entity.max_width = LIF_MAX_SIZE;
lif->entity.max_height = LIF_MAX_SIZE;
/*
* The LIF is never exposed to userspace, but media entity registration
* requires a function to be set. Use PROC_VIDEO_PIXEL_FORMATTER just to
* avoid triggering a WARN_ON(), the value won't be seen anywhere.
*/
ret = vsp1_entity_init(vsp1, &lif->entity, "lif", 2, &lif_ops,
MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER);
if (ret < 0)
return ERR_PTR(ret);
return lif;
}
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_lif.h -- R-Car VSP1 LCD Controller Interface
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_LIF_H__
#define __VSP1_LIF_H__
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define LIF_PAD_SINK 0
#define LIF_PAD_SOURCE 1
struct vsp1_lif {
struct vsp1_entity entity;
};
static inline struct vsp1_lif *to_lif(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_lif, entity.subdev);
}
struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1, unsigned int index);
#endif /* __VSP1_LIF_H__ */
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_lut.c -- R-Car VSP1 Look-Up Table
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_lut.h"
#define LUT_MIN_SIZE 4U
#define LUT_MAX_SIZE 8190U
#define LUT_SIZE 256
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_lut_write(struct vsp1_lut *lut,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* Controls
*/
#define V4L2_CID_VSP1_LUT_TABLE (V4L2_CID_USER_BASE | 0x1001)
static int lut_set_table(struct vsp1_lut *lut, struct v4l2_ctrl *ctrl)
{
struct vsp1_dl_body *dlb;
unsigned int i;
dlb = vsp1_dl_body_get(lut->pool);
if (!dlb)
return -ENOMEM;
for (i = 0; i < LUT_SIZE; ++i)
vsp1_dl_body_write(dlb, VI6_LUT_TABLE + 4 * i,
ctrl->p_new.p_u32[i]);
scoped_guard(spinlock_irq, &lut->lock) {
swap(lut->lut, dlb);
}
vsp1_dl_body_put(dlb);
return 0;
}
static int lut_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_lut *lut =
container_of(ctrl->handler, struct vsp1_lut, ctrls);
switch (ctrl->id) {
case V4L2_CID_VSP1_LUT_TABLE:
lut_set_table(lut, ctrl);
break;
}
return 0;
}
static const struct v4l2_ctrl_ops lut_ctrl_ops = {
.s_ctrl = lut_s_ctrl,
};
static const struct v4l2_ctrl_config lut_table_control = {
.ops = &lut_ctrl_ops,
.id = V4L2_CID_VSP1_LUT_TABLE,
.name = "Look-Up Table",
.type = V4L2_CTRL_TYPE_U32,
.min = 0x00000000,
.max = 0x00ffffff,
.step = 1,
.def = 0,
.dims = { LUT_SIZE },
};
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int lut_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static const struct v4l2_subdev_pad_ops lut_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = vsp1_subdev_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_subdev_set_pad_format,
};
static const struct v4l2_subdev_ops lut_ops = {
.core = &vsp1_entity_core_ops,
.pad = &lut_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void lut_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_lut *lut = to_lut(&entity->subdev);
vsp1_lut_write(lut, dlb, VI6_LUT_CTRL, VI6_LUT_CTRL_EN);
}
static void lut_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_lut *lut = to_lut(&entity->subdev);
struct vsp1_dl_body *lut_dlb;
scoped_guard(spinlock_irqsave, &lut->lock) {
lut_dlb = lut->lut;
lut->lut = NULL;
}
if (lut_dlb) {
vsp1_dl_list_add_body(dl, lut_dlb);
/* Release our local reference. */
vsp1_dl_body_put(lut_dlb);
}
}
static void lut_destroy(struct vsp1_entity *entity)
{
struct vsp1_lut *lut = to_lut(&entity->subdev);
vsp1_dl_body_pool_destroy(lut->pool);
}
static const struct vsp1_entity_operations lut_entity_ops = {
.configure_stream = lut_configure_stream,
.configure_frame = lut_configure_frame,
.destroy = lut_destroy,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1)
{
struct vsp1_lut *lut;
int ret;
lut = devm_kzalloc(vsp1->dev, sizeof(*lut), GFP_KERNEL);
if (lut == NULL)
return ERR_PTR(-ENOMEM);
spin_lock_init(&lut->lock);
lut->entity.ops = &lut_entity_ops;
lut->entity.type = VSP1_ENTITY_LUT;
lut->entity.codes = lut_codes;
lut->entity.num_codes = ARRAY_SIZE(lut_codes);
lut->entity.min_width = LUT_MIN_SIZE;
lut->entity.min_height = LUT_MIN_SIZE;
lut->entity.max_width = LUT_MAX_SIZE;
lut->entity.max_height = LUT_MAX_SIZE;
ret = vsp1_entity_init(vsp1, &lut->entity, "lut", 2, &lut_ops,
MEDIA_ENT_F_PROC_VIDEO_LUT);
if (ret < 0)
return ERR_PTR(ret);
/*
* Pre-allocate a body pool, with 3 bodies allowing a userspace update
* before the hardware has committed a previous set of tables, handling
* both the queued and pending dl entries.
*/
lut->pool = vsp1_dl_body_pool_create(vsp1, 3, LUT_SIZE, 0);
if (!lut->pool)
return ERR_PTR(-ENOMEM);
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&lut->ctrls, 1);
v4l2_ctrl_new_custom(&lut->ctrls, &lut_table_control, NULL);
lut->entity.subdev.ctrl_handler = &lut->ctrls;
if (lut->ctrls.error) {
dev_err(vsp1->dev, "lut: failed to initialize controls\n");
ret = lut->ctrls.error;
vsp1_entity_destroy(&lut->entity);
return ERR_PTR(ret);
}
v4l2_ctrl_handler_setup(&lut->ctrls);
return lut;
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_lut.h -- R-Car VSP1 Look-Up Table
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_LUT_H__
#define __VSP1_LUT_H__
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define LUT_PAD_SINK 0
#define LUT_PAD_SOURCE 1
struct vsp1_lut {
struct vsp1_entity entity;
struct v4l2_ctrl_handler ctrls;
spinlock_t lock;
struct vsp1_dl_body *lut;
struct vsp1_dl_body_pool *pool;
};
static inline struct vsp1_lut *to_lut(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_lut, entity.subdev);
}
struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1);
#endif /* __VSP1_LUT_H__ */
@@ -0,0 +1,685 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_pipe.c -- R-Car VSP1 Pipeline
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_brx.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_hgo.h"
#include "vsp1_hgt.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
#include "vsp1_uds.h"
/* -----------------------------------------------------------------------------
* Helper Functions
*/
static const struct vsp1_format_info vsp1_video_formats[] = {
{ V4L2_PIX_FMT_RGB332, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB_332, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 8, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ARGB444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XRGB444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_XRGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGBA444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_RGBX444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBX_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ABGR444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ABGR_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XBGR444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ABGR_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_BGRA444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_BGRA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_BGRX444, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_BGRA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ARGB555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XRGB555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_XRGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGBA555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_RGBX555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBX_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ABGR555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ABGR_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XBGR555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ABGR_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_BGRA555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_BGRA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_BGRX555, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_BGRA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB_565, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS,
1, { 16, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_BGR24, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_BGR_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 24, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGB24, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 24, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ABGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XBGR32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_BGRA32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_BGRX32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGBA32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_RGBX32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ARGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, true },
{ V4L2_PIX_FMT_XRGB32, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGBX1010102, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB10_RGB10A2_A2RGB10,
VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_RGBA1010102, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB10_RGB10A2_A2RGB10,
VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_ARGB2101010, MEDIA_BUS_FMT_ARGB8888_1X32,
VI6_FMT_RGB10_RGB10A2_A2RGB10,
VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_UYVY, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 16, 0, 0 }, false, false, 2, 1, false },
{ V4L2_PIX_FMT_YUYV, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 16, 0, 0 }, true, false, 2, 1, false },
{ V4L2_PIX_FMT_YVYU, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 16, 0, 0 }, true, true, 2, 1, false },
{ V4L2_PIX_FMT_NV12M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
2, { 8, 16, 0 }, false, false, 2, 2, false },
{ V4L2_PIX_FMT_NV21M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
2, { 8, 16, 0 }, false, true, 2, 2, false },
{ V4L2_PIX_FMT_NV16M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
2, { 8, 16, 0 }, false, false, 2, 1, false },
{ V4L2_PIX_FMT_NV61M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
2, { 8, 16, 0 }, false, true, 2, 1, false },
{ V4L2_PIX_FMT_YUV420M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, false, 2, 2, false },
{ V4L2_PIX_FMT_YVU420M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, true, 2, 2, false },
{ V4L2_PIX_FMT_YUV422M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, false, 2, 1, false },
{ V4L2_PIX_FMT_YVU422M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, true, 2, 1, false },
{ V4L2_PIX_FMT_YUV444M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_YVU444M, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
3, { 8, 8, 8 }, false, true, 1, 1, false },
{ V4L2_PIX_FMT_Y210, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 2, 1, false },
{ V4L2_PIX_FMT_Y212, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS,
1, { 32, 0, 0 }, false, false, 2, 1, false },
};
static const struct vsp1_format_info vsp1_video_gen2_formats[] = {
{ V4L2_PIX_FMT_VYUY, MEDIA_BUS_FMT_AYUV8_1X32,
VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 16, 0, 0 }, false, true, 2, 1, false },
};
static const struct vsp1_format_info vsp1_video_hsit_formats[] = {
{ V4L2_PIX_FMT_HSV24, MEDIA_BUS_FMT_AHSV8888_1X32,
VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 24, 0, 0 }, false, false, 1, 1, false },
{ V4L2_PIX_FMT_HSV32, MEDIA_BUS_FMT_AHSV8888_1X32,
VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS |
VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS,
1, { 32, 0, 0 }, false, false, 1, 1, false },
};
#define vsp1_for_each_format(info, formats) \
for (const struct vsp1_format_info *info = &formats[0]; \
info < formats + ARRAY_SIZE(formats); ++info)
/**
* vsp1_get_format_info - Retrieve format information for a 4CC
* @vsp1: the VSP1 device
* @fourcc: the format 4CC
*
* Return a pointer to the format information structure corresponding to the
* given V4L2 format 4CC, or NULL if no corresponding format can be found.
*/
const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1,
u32 fourcc)
{
vsp1_for_each_format(info, vsp1_video_formats) {
if (info->fourcc == fourcc)
return info;
}
if (vsp1->info->gen == 2) {
vsp1_for_each_format(info, vsp1_video_gen2_formats) {
if (info->fourcc == fourcc)
return info;
}
}
if (vsp1_feature(vsp1, VSP1_HAS_HSIT)) {
vsp1_for_each_format(info, vsp1_video_hsit_formats) {
if (info->fourcc == fourcc)
return info;
}
}
return NULL;
}
/**
* vsp1_get_format_info_by_index - Enumerate format information
* @vsp1: the VSP1 device
* @index: the format index
* @code: media bus code to limit enumeration
*
* Return a pointer to the format information structure corresponding to the
* given index, or NULL if the index exceeds the supported formats list. If the
* @code parameter is not zero, only formats compatible with the media bus code
* will be enumerated.
*/
const struct vsp1_format_info *
vsp1_get_format_info_by_index(struct vsp1_device *vsp1, unsigned int index,
u32 code)
{
if (!code) {
if (index < ARRAY_SIZE(vsp1_video_formats))
return &vsp1_video_formats[index];
if (vsp1->info->gen == 2) {
index -= ARRAY_SIZE(vsp1_video_formats);
if (index < ARRAY_SIZE(vsp1_video_gen2_formats))
return &vsp1_video_gen2_formats[index];
}
if (vsp1_feature(vsp1, VSP1_HAS_HSIT)) {
index -= ARRAY_SIZE(vsp1_video_gen2_formats);
if (index < ARRAY_SIZE(vsp1_video_hsit_formats))
return &vsp1_video_hsit_formats[index];
}
return NULL;
}
vsp1_for_each_format(info, vsp1_video_formats) {
if (info->mbus == code) {
if (!index)
return info;
index--;
}
}
if (vsp1->info->gen == 2) {
vsp1_for_each_format(info, vsp1_video_gen2_formats) {
if (info->mbus == code) {
if (!index)
return info;
index--;
}
}
}
if (vsp1_feature(vsp1, VSP1_HAS_HSIT)) {
vsp1_for_each_format(info, vsp1_video_hsit_formats) {
if (info->mbus == code) {
if (!index)
return info;
index--;
}
}
}
return NULL;
}
/**
* vsp1_adjust_color_space - Adjust color space fields in a format
* @code: the media bus code
* @colorspace: the colorspace
* @xfer_func: the transfer function
* @encoding: the encoding
* @quantization: the quantization
*
* This function adjusts all color space fields of a video device of subdev
* format structure, taking into account the requested format, requested color
* space and limitations of the VSP1. It should be used in the video device and
* subdev set format handlers.
*
* The colorspace and xfer_func fields are freely configurable, as they are out
* of scope for VSP processing. The encoding and quantization is hardcoded for
* non-YUV formats, and can be configured for YUV formats.
*/
void vsp1_adjust_color_space(u32 code, u32 *colorspace, u8 *xfer_func,
u8 *encoding, u8 *quantization)
{
if (*colorspace == V4L2_COLORSPACE_DEFAULT ||
*colorspace >= V4L2_COLORSPACE_LAST)
*colorspace = code == MEDIA_BUS_FMT_AYUV8_1X32
? V4L2_COLORSPACE_SMPTE170M
: V4L2_COLORSPACE_SRGB;
if (*xfer_func == V4L2_XFER_FUNC_DEFAULT ||
*xfer_func >= V4L2_XFER_FUNC_LAST)
*xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(*colorspace);
switch (code) {
case MEDIA_BUS_FMT_ARGB8888_1X32:
default:
*encoding = V4L2_YCBCR_ENC_601;
*quantization = V4L2_QUANTIZATION_FULL_RANGE;
break;
case MEDIA_BUS_FMT_AHSV8888_1X32:
*encoding = V4L2_HSV_ENC_256;
*quantization = V4L2_QUANTIZATION_FULL_RANGE;
break;
case MEDIA_BUS_FMT_AYUV8_1X32:
if (*encoding != V4L2_YCBCR_ENC_601 &&
*encoding != V4L2_YCBCR_ENC_709)
*encoding = V4L2_YCBCR_ENC_601;
if (*quantization != V4L2_QUANTIZATION_FULL_RANGE &&
*quantization != V4L2_QUANTIZATION_LIM_RANGE)
*quantization = V4L2_QUANTIZATION_LIM_RANGE;
break;
}
}
/* -----------------------------------------------------------------------------
* Pipeline Management
*/
void vsp1_pipeline_reset(struct vsp1_pipeline *pipe)
{
struct vsp1_entity *entity;
unsigned int i;
if (pipe->brx) {
struct vsp1_brx *brx = to_brx(&pipe->brx->subdev);
for (i = 0; i < ARRAY_SIZE(brx->inputs); ++i)
brx->inputs[i].rpf = NULL;
}
for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i)
pipe->inputs[i] = NULL;
pipe->output = NULL;
list_for_each_entry(entity, &pipe->entities, list_pipe)
entity->pipe = NULL;
INIT_LIST_HEAD(&pipe->entities);
pipe->state = VSP1_PIPELINE_STOPPED;
pipe->buffers_ready = 0;
pipe->num_inputs = 0;
pipe->brx = NULL;
pipe->hgo = NULL;
pipe->hgt = NULL;
pipe->iif = NULL;
pipe->lif = NULL;
pipe->uds = NULL;
}
void vsp1_pipeline_init(struct vsp1_pipeline *pipe)
{
mutex_init(&pipe->lock);
spin_lock_init(&pipe->irqlock);
init_waitqueue_head(&pipe->wq);
kref_init(&pipe->kref);
INIT_LIST_HEAD(&pipe->entities);
pipe->state = VSP1_PIPELINE_STOPPED;
}
void __vsp1_pipeline_dump(struct _ddebug *dbg, struct vsp1_pipeline *pipe,
const char *msg)
{
struct vsp1_device *vsp1 = pipe->output->entity.vsp1;
struct vsp1_entity *entity;
bool first = true;
printk(KERN_DEBUG "%s: %s: pipe: ", dev_name(vsp1->dev), msg);
list_for_each_entry(entity, &pipe->entities, list_pipe) {
const char *name;
name = strchrnul(entity->subdev.name, ' ');
name = name ? name + 1 : entity->subdev.name;
pr_cont("%s%s", first ? "" : ", ", name);
first = false;
}
pr_cont("\n");
}
/* Must be called with the pipe irqlock held. */
void vsp1_pipeline_run(struct vsp1_pipeline *pipe)
{
struct vsp1_device *vsp1 = pipe->output->entity.vsp1;
lockdep_assert_held(&pipe->irqlock);
if (pipe->state == VSP1_PIPELINE_STOPPED) {
vsp1_write(vsp1, VI6_CMD(pipe->output->entity.index),
VI6_CMD_STRCMD);
pipe->state = VSP1_PIPELINE_RUNNING;
}
pipe->buffers_ready = 0;
}
bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe)
{
guard(spinlock_irqsave)(&pipe->irqlock);
return pipe->state == VSP1_PIPELINE_STOPPED;
}
int vsp1_pipeline_stop(struct vsp1_pipeline *pipe)
{
struct vsp1_device *vsp1 = pipe->output->entity.vsp1;
struct vsp1_entity *entity;
int ret;
if (pipe->lif) {
/*
* When using display lists in continuous frame mode the only
* way to stop the pipeline is to reset the hardware.
*/
ret = vsp1_reset_wpf(vsp1, pipe->output->entity.index);
if (ret == 0) {
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
pipe->state = VSP1_PIPELINE_STOPPED;
}
}
} else {
/* Otherwise just request a stop and wait. */
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
if (pipe->state == VSP1_PIPELINE_RUNNING)
pipe->state = VSP1_PIPELINE_STOPPING;
}
ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe),
msecs_to_jiffies(500));
ret = ret == 0 ? -ETIMEDOUT : 0;
}
list_for_each_entry(entity, &pipe->entities, list_pipe) {
if (entity->route && entity->route->reg)
vsp1_write(vsp1, entity->route->reg,
VI6_DPR_NODE_UNUSED);
}
if (pipe->hgo)
vsp1_write(vsp1, VI6_DPR_HGO_SMPPT,
(7 << VI6_DPR_SMPPT_TGW_SHIFT) |
(VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT));
if (pipe->hgt)
vsp1_write(vsp1, VI6_DPR_HGT_SMPPT,
(7 << VI6_DPR_SMPPT_TGW_SHIFT) |
(VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT));
vsp1_wpf_stop(pipe->output);
return ret;
}
bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe)
{
unsigned int mask;
mask = ((1 << pipe->num_inputs) - 1) << 1;
if (!pipe->lif)
mask |= 1 << 0;
return pipe->buffers_ready == mask;
}
void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe)
{
unsigned int flags;
if (pipe == NULL)
return;
/*
* If the DL commit raced with the frame end interrupt, the commit ends
* up being postponed by one frame. The returned flags tell whether the
* active frame was finished or postponed.
*/
flags = vsp1_dlm_irq_frame_end(pipe->output->dlm);
if (pipe->hgo)
vsp1_hgo_frame_end(pipe->hgo);
if (pipe->hgt)
vsp1_hgt_frame_end(pipe->hgt);
/*
* Regardless of frame completion we still need to notify the pipe
* frame_end to account for vblank events.
*/
if (pipe->frame_end)
pipe->frame_end(pipe, flags);
pipe->sequence++;
}
/*
* Propagate the alpha value through the pipeline.
*
* As the UDS has restricted scaling capabilities when the alpha component needs
* to be scaled, we disable alpha scaling when the UDS input has a fixed alpha
* value. The UDS then outputs a fixed alpha value which needs to be programmed
* from the input RPF alpha.
*/
void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe,
struct vsp1_dl_body *dlb, unsigned int alpha)
{
if (!pipe->uds)
return;
/*
* The BRU and BRS background color has a fixed alpha value set to 255,
* the output alpha value is thus always equal to 255.
*/
if (pipe->uds_input->type == VSP1_ENTITY_BRU ||
pipe->uds_input->type == VSP1_ENTITY_BRS)
alpha = 255;
vsp1_uds_set_alpha(pipe->uds, dlb, alpha);
}
/* -----------------------------------------------------------------------------
* VSP1 Partition Algorithm support
*/
/*
* Propagate the partition calculations through the pipeline
*
* Work backwards through the pipe, allowing each entity to update the partition
* parameters based on its configuration, and the entity connected to its
* source. Each entity must produce the partition required for the previous
* entity in the pipeline.
*/
static void vsp1_pipeline_propagate_partition(struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int index,
struct v4l2_rect *window)
{
struct vsp1_entity *entity;
list_for_each_entry_reverse(entity, &pipe->entities, list_pipe) {
if (entity->ops->partition)
entity->ops->partition(entity, entity->state, pipe,
partition, index, window);
}
}
/*
* vsp1_pipeline_calculate_partition - Calculate pipeline configuration for a
* partition
*
* @pipe: the pipeline
* @partition: partition that will hold the calculated values
* @div_size: pre-determined maximum partition division size
* @index: partition index
*/
void vsp1_pipeline_calculate_partition(struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int div_size,
unsigned int index)
{
const struct v4l2_mbus_framefmt *format;
struct v4l2_rect window;
unsigned int modulus;
/*
* Partitions are computed on the size before rotation, use the format
* at the WPF sink.
*/
format = v4l2_subdev_state_get_format(pipe->output->entity.state,
RWPF_PAD_SINK);
/* Initialise the partition with sane starting conditions. */
window.left = index * div_size;
window.width = div_size;
window.top = 0;
window.height = format->height;
modulus = format->width % div_size;
/*
* We need to prevent the last partition from being smaller than the
* *minimum* width of the hardware capabilities.
*
* If the modulus is less than half of the partition size,
* the penultimate partition is reduced to half, which is added
* to the final partition: |1234|1234|1234|12|341|
* to prevent this: |1234|1234|1234|1234|1|.
*/
if (modulus) {
/*
* pipe->partitions is 1 based, whilst index is a 0 based index.
* Normalise this locally.
*/
unsigned int partitions = pipe->partitions - 1;
if (modulus < div_size / 2) {
if (index == partitions - 1) {
/* Halve the penultimate partition. */
window.width = div_size / 2;
} else if (index == partitions) {
/* Increase the final partition. */
window.width = (div_size / 2) + modulus;
window.left -= div_size / 2;
}
} else if (index == partitions) {
window.width = modulus;
}
}
vsp1_pipeline_propagate_partition(pipe, partition, index, &window);
}
@@ -0,0 +1,189 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_pipe.h -- R-Car VSP1 Pipeline
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_PIPE_H__
#define __VSP1_PIPE_H__
#include <linux/dynamic_debug.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <media/media-entity.h>
struct vsp1_dl_list;
struct vsp1_rwpf;
/*
* struct vsp1_format_info - VSP1 video format description
* @fourcc: V4L2 pixel format FCC identifier
* @mbus: media bus format code
* @hwfmt: VSP1 hardware format
* @swap: swap register control
* @planes: number of planes
* @bpp: bits per pixel
* @swap_yc: the Y and C components are swapped (Y comes before C)
* @swap_uv: the U and V components are swapped (V comes before U)
* @hsub: horizontal subsampling factor
* @vsub: vertical subsampling factor
* @alpha: has an alpha channel
*/
struct vsp1_format_info {
u32 fourcc;
unsigned int mbus;
unsigned int hwfmt;
unsigned int swap;
unsigned int planes;
unsigned int bpp[3];
bool swap_yc;
bool swap_uv;
unsigned int hsub;
unsigned int vsub;
bool alpha;
};
enum vsp1_pipeline_state {
VSP1_PIPELINE_STOPPED,
VSP1_PIPELINE_RUNNING,
VSP1_PIPELINE_STOPPING,
};
/*
* struct vsp1_partition - A description of a slice for the partition algorithm
* @rpf: The RPF partition window configuration
* @uds_sink: The UDS input partition window configuration
* @uds_source: The UDS output partition window configuration
* @sru: The SRU partition window configuration
* @wpf: The WPF partition window configuration
*/
struct vsp1_partition {
struct v4l2_rect rpf[VSP1_MAX_RPF];
struct v4l2_rect uds_sink;
struct v4l2_rect uds_source;
struct v4l2_rect sru;
struct v4l2_rect wpf;
};
/*
* struct vsp1_pipeline - A VSP1 hardware pipeline
* @pipe: the media pipeline
* @irqlock: protects the pipeline state
* @state: current state
* @wq: wait queue to wait for state change completion
* @frame_end: frame end interrupt handler
* @lock: protects the pipeline use count and stream count
* @kref: pipeline reference count
* @stream_count: number of streaming video nodes
* @buffers_ready: bitmask of RPFs and WPFs with at least one buffer available
* @sequence: frame sequence number
* @num_inputs: number of RPFs
* @inputs: array of RPFs in the pipeline (indexed by RPF index)
* @output: WPF at the output of the pipeline
* @brx: BRx entity, if present
* @hgo: HGO entity, if present
* @hgt: HGT entity, if present
* @lif: LIF entity, if present
* @uds: UDS entity, if present
* @uds_input: entity at the input of the UDS, if the UDS is present
* @entities: list of entities in the pipeline
* @stream_config: cached stream configuration for video pipelines
* @configured: when false the @stream_config shall be written to the hardware
* @interlaced: True when the pipeline is configured in interlaced mode
* @partitions: The number of partitions used to process one frame
* @part_table: The pre-calculated partitions used by the pipeline
*/
struct vsp1_pipeline {
struct media_pipeline pipe;
spinlock_t irqlock;
enum vsp1_pipeline_state state;
wait_queue_head_t wq;
void (*frame_end)(struct vsp1_pipeline *pipe, unsigned int completion);
struct mutex lock;
struct kref kref;
unsigned int stream_count;
unsigned int buffers_ready;
unsigned int sequence;
unsigned int num_inputs;
struct vsp1_rwpf *inputs[VSP1_MAX_RPF];
struct vsp1_rwpf *output;
struct vsp1_entity *brx;
struct vsp1_entity *hgo;
struct vsp1_entity *hgt;
struct vsp1_entity *iif;
struct vsp1_entity *lif;
struct vsp1_entity *uds;
struct vsp1_entity *uds_input;
/*
* The order of this list must be identical to the order of the entities
* in the pipeline, as it is assumed by the partition algorithm that we
* can walk this list in sequence.
*/
struct list_head entities;
struct vsp1_dl_body *stream_config;
bool configured;
bool interlaced;
unsigned int partitions;
struct vsp1_partition *part_table;
u32 underrun_count;
};
void vsp1_pipeline_reset(struct vsp1_pipeline *pipe);
void vsp1_pipeline_init(struct vsp1_pipeline *pipe);
void __vsp1_pipeline_dump(struct _ddebug *, struct vsp1_pipeline *pipe,
const char *msg);
#if defined(CONFIG_DYNAMIC_DEBUG) || \
(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
#define vsp1_pipeline_dump(pipe, msg) \
_dynamic_func_call("vsp1_pipeline_dump()", __vsp1_pipeline_dump, pipe, msg)
#elif defined(DEBUG)
#define vsp1_pipeline_dump(pipe, msg) \
__vsp1_pipeline_dump(NULL, pipe, msg)
#else
#define vsp1_pipeline_dump(pipe, msg) \
({ \
if (0) \
__vsp1_pipeline_dump(NULL, pipe, msg); \
})
#endif
void vsp1_pipeline_run(struct vsp1_pipeline *pipe);
bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe);
int vsp1_pipeline_stop(struct vsp1_pipeline *pipe);
bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe);
void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe);
void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe,
struct vsp1_dl_body *dlb,
unsigned int alpha);
void vsp1_pipeline_calculate_partition(struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int div_size,
unsigned int index);
const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1,
u32 fourcc);
const struct vsp1_format_info *
vsp1_get_format_info_by_index(struct vsp1_device *vsp1, unsigned int index,
u32 code);
void vsp1_adjust_color_space(u32 code, u32 *colorspace, u8 *xfer_func,
u8 *encoding, u8 *quantization);
#endif /* __VSP1_PIPE_H__ */
@@ -0,0 +1,895 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_regs.h -- R-Car VSP1 Registers Definitions
*
* Copyright (C) 2013 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_REGS_H__
#define __VSP1_REGS_H__
/* -----------------------------------------------------------------------------
* General Control Registers
*/
#define VI6_CMD(n) (0x0000 + (n) * 4)
#define VI6_CMD_UPDHDR BIT(4)
#define VI6_CMD_STRCMD BIT(0)
#define VI6_CLK_DCSWT 0x0018
#define VI6_CLK_DCSWT_CSTPW_MASK (0xff << 8)
#define VI6_CLK_DCSWT_CSTPW_SHIFT 8
#define VI6_CLK_DCSWT_CSTRW_MASK (0xff << 0)
#define VI6_CLK_DCSWT_CSTRW_SHIFT 0
#define VI6_SRESET 0x0028
#define VI6_SRESET_SRTS(n) BIT(n)
#define VI6_STATUS 0x0038
#define VI6_STATUS_FLD_STD(n) BIT((n) + 28)
#define VI6_STATUS_SYS_ACT(n) BIT((n) + 8)
#define VI6_WPF_IRQ_ENB(n) (0x0048 + (n) * 12)
#define VI6_WPF_IRQ_ENB_UNDE BIT(16)
#define VI6_WPF_IRQ_ENB_DFEE BIT(1)
#define VI6_WPF_IRQ_ENB_FREE BIT(0)
#define VI6_WPF_IRQ_STA(n) (0x004c + (n) * 12)
#define VI6_WPF_IRQ_STA_UND BIT(16)
#define VI6_WPF_IRQ_STA_DFE BIT(1)
#define VI6_WPF_IRQ_STA_FRE BIT(0)
#define VI6_DISP_IRQ_ENB(n) (0x0078 + (n) * 60)
#define VI6_DISP_IRQ_ENB_DSTE BIT(8)
#define VI6_DISP_IRQ_ENB_MAEE BIT(5)
#define VI6_DISP_IRQ_ENB_LNEE(n) BIT(n)
#define VI6_DISP_IRQ_STA(n) (0x007c + (n) * 60)
#define VI6_DISP_IRQ_STA_DST BIT(8)
#define VI6_DISP_IRQ_STA_MAE BIT(5)
#define VI6_DISP_IRQ_STA_LNE(n) BIT(n)
#define VI6_WPF_LINE_COUNT(n) (0x0084 + (n) * 4)
#define VI6_WPF_LINE_COUNT_MASK (0x1fffff << 0)
/* -----------------------------------------------------------------------------
* Display List Control Registers
*/
#define VI6_DL_CTRL 0x0100
#define VI6_DL_CTRL_AR_WAIT_MASK (0xffff << 16)
#define VI6_DL_CTRL_AR_WAIT_SHIFT 16
#define VI6_DL_CTRL_DC2 BIT(12)
#define VI6_DL_CTRL_DC1 BIT(8)
#define VI6_DL_CTRL_DC0 BIT(4)
#define VI6_DL_CTRL_CFM0 BIT(2)
#define VI6_DL_CTRL_NH0 BIT(1)
#define VI6_DL_CTRL_DLE BIT(0)
#define VI6_DL_HDR_ADDR(n) (0x0104 + (n) * 4)
#define VI6_DL_SWAP 0x0114
#define VI6_DL_SWAP_LWS BIT(2)
#define VI6_DL_SWAP_WDS BIT(1)
#define VI6_DL_SWAP_BTS BIT(0)
#define VI6_DL_EXT_CTRL(n) (0x011c + (n) * 36)
#define VI6_DL_EXT_CTRL_NWE BIT(16)
#define VI6_DL_EXT_CTRL_POLINT_MASK (0x3f << 8)
#define VI6_DL_EXT_CTRL_POLINT_SHIFT 8
#define VI6_DL_EXT_CTRL_DLPRI BIT(5)
#define VI6_DL_EXT_CTRL_EXPRI BIT(4)
#define VI6_DL_EXT_CTRL_EXT BIT(0)
#define VI6_DL_EXT_AUTOFLD_INT BIT(0)
#define VI6_DL_BODY_SIZE 0x0120
#define VI6_DL_BODY_SIZE_UPD BIT(24)
#define VI6_DL_BODY_SIZE_BS_MASK (0x1ffff << 0)
#define VI6_DL_BODY_SIZE_BS_SHIFT 0
/* -----------------------------------------------------------------------------
* RPF Control Registers
*/
#define VI6_RPF_OFFSET 0x100
#define VI6_RPF_SRC_BSIZE 0x0300
#define VI6_RPF_SRC_BSIZE_BHSIZE_MASK (0x1fff << 16)
#define VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT 16
#define VI6_RPF_SRC_BSIZE_BVSIZE_MASK (0x1fff << 0)
#define VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT 0
#define VI6_RPF_SRC_ESIZE 0x0304
#define VI6_RPF_SRC_ESIZE_EHSIZE_MASK (0x1fff << 16)
#define VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT 16
#define VI6_RPF_SRC_ESIZE_EVSIZE_MASK (0x1fff << 0)
#define VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT 0
#define VI6_RPF_INFMT 0x0308
#define VI6_RPF_INFMT_VIR BIT(28)
#define VI6_RPF_INFMT_CIPM BIT(16)
#define VI6_RPF_INFMT_SPYCS BIT(15)
#define VI6_RPF_INFMT_SPUVS BIT(14)
#define VI6_RPF_INFMT_CEXT_ZERO (0 << 12)
#define VI6_RPF_INFMT_CEXT_EXT (1 << 12)
#define VI6_RPF_INFMT_CEXT_ONE (2 << 12)
#define VI6_RPF_INFMT_CEXT_MASK (3 << 12)
#define VI6_RPF_INFMT_RDTM_BT601 (0 << 9)
#define VI6_RPF_INFMT_RDTM_BT601_EXT (1 << 9)
#define VI6_RPF_INFMT_RDTM_BT709 (2 << 9)
#define VI6_RPF_INFMT_RDTM_BT709_EXT (3 << 9)
#define VI6_RPF_INFMT_RDTM_MASK (7 << 9)
#define VI6_RPF_INFMT_CSC BIT(8)
#define VI6_RPF_INFMT_RDFMT_MASK (0x7f << 0)
#define VI6_RPF_INFMT_RDFMT_SHIFT 0
#define VI6_RPF_DSWAP 0x030c
#define VI6_RPF_DSWAP_A_LLS BIT(11)
#define VI6_RPF_DSWAP_A_LWS BIT(10)
#define VI6_RPF_DSWAP_A_WDS BIT(9)
#define VI6_RPF_DSWAP_A_BTS BIT(8)
#define VI6_RPF_DSWAP_P_LLS BIT(3)
#define VI6_RPF_DSWAP_P_LWS BIT(2)
#define VI6_RPF_DSWAP_P_WDS BIT(1)
#define VI6_RPF_DSWAP_P_BTS BIT(0)
#define VI6_RPF_LOC 0x0310
#define VI6_RPF_LOC_HCOORD_MASK (0x1fff << 16)
#define VI6_RPF_LOC_HCOORD_SHIFT 16
#define VI6_RPF_LOC_VCOORD_MASK (0x1fff << 0)
#define VI6_RPF_LOC_VCOORD_SHIFT 0
#define VI6_RPF_ALPH_SEL 0x0314
#define VI6_RPF_ALPH_SEL_ASEL_PACKED (0 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_8B_PLANE (1 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_SELECT (2 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_1B_PLANE (3 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_FIXED (4 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_MASK (7 << 28)
#define VI6_RPF_ALPH_SEL_ASEL_SHIFT 28
#define VI6_RPF_ALPH_SEL_IROP_MASK (0xf << 24)
#define VI6_RPF_ALPH_SEL_IROP_SHIFT 24
#define VI6_RPF_ALPH_SEL_BSEL BIT(23)
#define VI6_RPF_ALPH_SEL_AEXT_ZERO (0 << 18)
#define VI6_RPF_ALPH_SEL_AEXT_EXT (1 << 18)
#define VI6_RPF_ALPH_SEL_AEXT_ONE (2 << 18)
#define VI6_RPF_ALPH_SEL_AEXT_MASK (3 << 18)
#define VI6_RPF_ALPH_SEL_ALPHA1_MASK (0xff << 8)
#define VI6_RPF_ALPH_SEL_ALPHA1_SHIFT 8
#define VI6_RPF_ALPH_SEL_ALPHA0_MASK (0xff << 0)
#define VI6_RPF_ALPH_SEL_ALPHA0_SHIFT 0
#define VI6_RPF_VRTCOL_SET 0x0318
#define VI6_RPF_VRTCOL_SET_LAYA_MASK (0xff << 24)
#define VI6_RPF_VRTCOL_SET_LAYA_SHIFT 24
#define VI6_RPF_VRTCOL_SET_LAYR_MASK (0xff << 16)
#define VI6_RPF_VRTCOL_SET_LAYR_SHIFT 16
#define VI6_RPF_VRTCOL_SET_LAYG_MASK (0xff << 8)
#define VI6_RPF_VRTCOL_SET_LAYG_SHIFT 8
#define VI6_RPF_VRTCOL_SET_LAYB_MASK (0xff << 0)
#define VI6_RPF_VRTCOL_SET_LAYB_SHIFT 0
#define VI6_RPF_MSK_CTRL 0x031c
#define VI6_RPF_MSK_CTRL_MSK_EN BIT(24)
#define VI6_RPF_MSK_CTRL_MGR_MASK (0xff << 16)
#define VI6_RPF_MSK_CTRL_MGR_SHIFT 16
#define VI6_RPF_MSK_CTRL_MGG_MASK (0xff << 8)
#define VI6_RPF_MSK_CTRL_MGG_SHIFT 8
#define VI6_RPF_MSK_CTRL_MGB_MASK (0xff << 0)
#define VI6_RPF_MSK_CTRL_MGB_SHIFT 0
#define VI6_RPF_MSK_SET0 0x0320
#define VI6_RPF_MSK_SET1 0x0324
#define VI6_RPF_MSK_SET_MSA_MASK (0xff << 24)
#define VI6_RPF_MSK_SET_MSA_SHIFT 24
#define VI6_RPF_MSK_SET_MSR_MASK (0xff << 16)
#define VI6_RPF_MSK_SET_MSR_SHIFT 16
#define VI6_RPF_MSK_SET_MSG_MASK (0xff << 8)
#define VI6_RPF_MSK_SET_MSG_SHIFT 8
#define VI6_RPF_MSK_SET_MSB_MASK (0xff << 0)
#define VI6_RPF_MSK_SET_MSB_SHIFT 0
#define VI6_RPF_CKEY_CTRL 0x0328
#define VI6_RPF_CKEY_CTRL_CV BIT(4)
#define VI6_RPF_CKEY_CTRL_SAPE1 BIT(1)
#define VI6_RPF_CKEY_CTRL_SAPE0 BIT(0)
#define VI6_RPF_CKEY_SET0 0x032c
#define VI6_RPF_CKEY_SET1 0x0330
#define VI6_RPF_CKEY_SET_AP_MASK (0xff << 24)
#define VI6_RPF_CKEY_SET_AP_SHIFT 24
#define VI6_RPF_CKEY_SET_R_MASK (0xff << 16)
#define VI6_RPF_CKEY_SET_R_SHIFT 16
#define VI6_RPF_CKEY_SET_GY_MASK (0xff << 8)
#define VI6_RPF_CKEY_SET_GY_SHIFT 8
#define VI6_RPF_CKEY_SET_B_MASK (0xff << 0)
#define VI6_RPF_CKEY_SET_B_SHIFT 0
#define VI6_RPF_SRCM_PSTRIDE 0x0334
#define VI6_RPF_SRCM_PSTRIDE_Y_SHIFT 16
#define VI6_RPF_SRCM_PSTRIDE_C_SHIFT 0
#define VI6_RPF_SRCM_ASTRIDE 0x0338
#define VI6_RPF_SRCM_PSTRIDE_A_SHIFT 0
#define VI6_RPF_SRCM_ADDR_Y 0x033c
#define VI6_RPF_SRCM_ADDR_C0 0x0340
#define VI6_RPF_SRCM_ADDR_C1 0x0344
#define VI6_RPF_SRCM_ADDR_AI 0x0348
#define VI6_RPF_MULT_ALPHA 0x036c
#define VI6_RPF_MULT_ALPHA_A_MMD_NONE (0 << 12)
#define VI6_RPF_MULT_ALPHA_A_MMD_RATIO (1 << 12)
#define VI6_RPF_MULT_ALPHA_P_MMD_NONE (0 << 8)
#define VI6_RPF_MULT_ALPHA_P_MMD_RATIO (1 << 8)
#define VI6_RPF_MULT_ALPHA_P_MMD_IMAGE (2 << 8)
#define VI6_RPF_MULT_ALPHA_P_MMD_BOTH (3 << 8)
#define VI6_RPF_MULT_ALPHA_RATIO_MASK (0xff << 0)
#define VI6_RPF_MULT_ALPHA_RATIO_SHIFT 0
#define VI6_RPF_EXT_INFMT0 0x0370
#define VI6_RPF_EXT_INFMT0_F2B BIT(12)
#define VI6_RPF_EXT_INFMT0_IPBD_Y_8 (0 << 8)
#define VI6_RPF_EXT_INFMT0_IPBD_Y_10 (1 << 8)
#define VI6_RPF_EXT_INFMT0_IPBD_Y_12 (2 << 8)
#define VI6_RPF_EXT_INFMT0_IPBD_C_8 (0 << 4)
#define VI6_RPF_EXT_INFMT0_IPBD_C_10 (1 << 4)
#define VI6_RPF_EXT_INFMT0_IPBD_C_12 (2 << 4)
#define VI6_RPF_EXT_INFMT0_BYPP_M1_RGB10 (3 << 0)
#define VI6_RPF_EXT_INFMT1 0x0374
#define VI6_RPF_EXT_INFMT1_PACK_CPOS(a, b, c, d) \
(((a) << 24) | ((b) << 16) | ((c) << 8) | ((d) << 0))
#define VI6_RPF_EXT_INFMT2 0x0378
#define VI6_RPF_EXT_INFMT2_PACK_CLEN(a, b, c, d) \
(((a) << 24) | ((b) << 16) | ((c) << 8) | ((d) << 0))
#define VI6_RPF_BRDITH_CTRL 0x03e0
#define VI6_RPF_BRDITH_CTRL_ODE BIT(8)
#define VI6_RPF_BRDITH_CTRL_CBRM BIT(0)
/* -----------------------------------------------------------------------------
* IIF Control Registers
*/
#define VI6_IIF_CTRL 0x0608
#define VI6_IIF_CTRL_CTRL 0x13
/* -----------------------------------------------------------------------------
* WPF Control Registers
*/
#define VI6_WPF_OFFSET 0x100
#define VI6_WPF_SRCRPF 0x1000
#define VI6_WPF_SRCRPF_VIRACT_DIS (0 << 28)
#define VI6_WPF_SRCRPF_VIRACT_SUB (1 << 28)
#define VI6_WPF_SRCRPF_VIRACT_MST (2 << 28)
#define VI6_WPF_SRCRPF_VIRACT_MASK (3 << 28)
#define VI6_WPF_SRCRPF_VIRACT2_DIS (0 << 24)
#define VI6_WPF_SRCRPF_VIRACT2_SUB (1 << 24)
#define VI6_WPF_SRCRPF_VIRACT2_MST (2 << 24)
#define VI6_WPF_SRCRPF_VIRACT2_MASK (3 << 24)
#define VI6_WPF_SRCRPF_RPF_ACT_DIS(n) (0 << ((n) * 2))
#define VI6_WPF_SRCRPF_RPF_ACT_SUB(n) (1 << ((n) * 2))
#define VI6_WPF_SRCRPF_RPF_ACT_MST(n) (2 << ((n) * 2))
#define VI6_WPF_SRCRPF_RPF_ACT_MASK(n) (3 << ((n) * 2))
#define VI6_WPF_HSZCLIP 0x1004
#define VI6_WPF_VSZCLIP 0x1008
#define VI6_WPF_SZCLIP_EN BIT(28)
#define VI6_WPF_SZCLIP_OFST_MASK (0xff << 16)
#define VI6_WPF_SZCLIP_OFST_SHIFT 16
#define VI6_WPF_SZCLIP_SIZE_MASK (0xfff << 0)
#define VI6_WPF_SZCLIP_SIZE_SHIFT 0
#define VI6_WPF_OUTFMT 0x100c
#define VI6_WPF_OUTFMT_PDV_MASK (0xff << 24)
#define VI6_WPF_OUTFMT_PDV_SHIFT 24
#define VI6_WPF_OUTFMT_PXA BIT(23)
#define VI6_WPF_OUTFMT_ROT BIT(18)
#define VI6_WPF_OUTFMT_HFLP BIT(17)
#define VI6_WPF_OUTFMT_FLP BIT(16)
#define VI6_WPF_OUTFMT_SPYCS BIT(15)
#define VI6_WPF_OUTFMT_SPUVS BIT(14)
#define VI6_WPF_OUTFMT_DITH_DIS (0 << 12)
#define VI6_WPF_OUTFMT_DITH_EN (3 << 12)
#define VI6_WPF_OUTFMT_DITH_MASK (3 << 12)
#define VI6_WPF_OUTFMT_WRTM_BT601 (0 << 9)
#define VI6_WPF_OUTFMT_WRTM_BT601_EXT (1 << 9)
#define VI6_WPF_OUTFMT_WRTM_BT709 (2 << 9)
#define VI6_WPF_OUTFMT_WRTM_BT709_EXT (3 << 9)
#define VI6_WPF_OUTFMT_WRTM_MASK (7 << 9)
#define VI6_WPF_OUTFMT_CSC BIT(8)
#define VI6_WPF_OUTFMT_WRFMT_MASK (0x7f << 0)
#define VI6_WPF_OUTFMT_WRFMT_SHIFT 0
#define VI6_WPF_DSWAP 0x1010
#define VI6_WPF_DSWAP_P_LLS BIT(3)
#define VI6_WPF_DSWAP_P_LWS BIT(2)
#define VI6_WPF_DSWAP_P_WDS BIT(1)
#define VI6_WPF_DSWAP_P_BTS BIT(0)
#define VI6_WPF_RNDCTRL 0x1014
#define VI6_WPF_RNDCTRL_CBRM BIT(28)
#define VI6_WPF_RNDCTRL_ABRM_TRUNC (0 << 24)
#define VI6_WPF_RNDCTRL_ABRM_ROUND (1 << 24)
#define VI6_WPF_RNDCTRL_ABRM_THRESH (2 << 24)
#define VI6_WPF_RNDCTRL_ABRM_MASK (3 << 24)
#define VI6_WPF_RNDCTRL_ATHRESH_MASK (0xff << 16)
#define VI6_WPF_RNDCTRL_ATHRESH_SHIFT 16
#define VI6_WPF_RNDCTRL_CLMD_FULL (0 << 12)
#define VI6_WPF_RNDCTRL_CLMD_CLIP (1 << 12)
#define VI6_WPF_RNDCTRL_CLMD_EXT (2 << 12)
#define VI6_WPF_RNDCTRL_CLMD_MASK (3 << 12)
#define VI6_WPF_ROT_CTRL 0x1018
#define VI6_WPF_ROT_CTRL_LN16 BIT(17)
#define VI6_WPF_ROT_CTRL_LMEM_WD_MASK (0x1fff << 0)
#define VI6_WPF_ROT_CTRL_LMEM_WD_SHIFT 0
#define VI6_WPF_DSTM_STRIDE_Y 0x101c
#define VI6_WPF_DSTM_STRIDE_C 0x1020
#define VI6_WPF_DSTM_ADDR_Y 0x1024
#define VI6_WPF_DSTM_ADDR_C0 0x1028
#define VI6_WPF_DSTM_ADDR_C1 0x102c
#define VI6_WPF_WRBCK_CTRL(n) (0x1034 + (n) * 0x100)
#define VI6_WPF_WRBCK_CTRL_WBMD BIT(0)
/* -----------------------------------------------------------------------------
* UIF Control Registers
*/
#define VI6_UIF_OFFSET 0x100
#define VI6_UIF_DISCOM_DOCMCR 0x1c00
#define VI6_UIF_DISCOM_DOCMCR_CMPRU BIT(16)
#define VI6_UIF_DISCOM_DOCMCR_CMPR BIT(0)
#define VI6_UIF_DISCOM_DOCMSTR 0x1c04
#define VI6_UIF_DISCOM_DOCMSTR_CMPPRE BIT(1)
#define VI6_UIF_DISCOM_DOCMSTR_CMPST BIT(0)
#define VI6_UIF_DISCOM_DOCMCLSTR 0x1c08
#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLPRE BIT(1)
#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLST BIT(0)
#define VI6_UIF_DISCOM_DOCMIENR 0x1c0c
#define VI6_UIF_DISCOM_DOCMIENR_CMPPREIEN BIT(1)
#define VI6_UIF_DISCOM_DOCMIENR_CMPIEN BIT(0)
#define VI6_UIF_DISCOM_DOCMMDR 0x1c10
#define VI6_UIF_DISCOM_DOCMMDR_INTHRH(n) ((n) << 16)
#define VI6_UIF_DISCOM_DOCMPMR 0x1c14
#define VI6_UIF_DISCOM_DOCMPMR_CMPDFF(n) ((n) << 17)
#define VI6_UIF_DISCOM_DOCMPMR_CMPDFA(n) ((n) << 8)
#define VI6_UIF_DISCOM_DOCMPMR_CMPDAUF BIT(7)
#define VI6_UIF_DISCOM_DOCMPMR_SEL(n) ((n) << 0)
#define VI6_UIF_DISCOM_DOCMECRCR 0x1c18
#define VI6_UIF_DISCOM_DOCMCCRCR 0x1c1c
#define VI6_UIF_DISCOM_DOCMSPXR 0x1c20
#define VI6_UIF_DISCOM_DOCMSPYR 0x1c24
#define VI6_UIF_DISCOM_DOCMSZXR 0x1c28
#define VI6_UIF_DISCOM_DOCMSZYR 0x1c2c
/* -----------------------------------------------------------------------------
* DPR Control Registers
*/
#define VI6_DPR_RPF_ROUTE(n) (0x2000 + (n) * 4)
#define VI6_DPR_WPF_FPORCH(n) (0x2014 + (n) * 4)
#define VI6_DPR_WPF_FPORCH_FP_WPFN (5 << 8)
#define VI6_DPR_SRU_ROUTE 0x2024
#define VI6_DPR_UDS_ROUTE(n) (0x2028 + (n) * 4)
#define VI6_DPR_LUT_ROUTE 0x203c
#define VI6_DPR_CLU_ROUTE 0x2040
#define VI6_DPR_HST_ROUTE 0x2044
#define VI6_DPR_HSI_ROUTE 0x2048
#define VI6_DPR_BRU_ROUTE 0x204c
#define VI6_DPR_ROUTE_IIFSEL BIT(28)
#define VI6_DPR_ILV_BRS_ROUTE 0x2050
#define VI6_DPR_ROUTE_BRSSEL BIT(28)
#define VI6_DPR_ROUTE_FXA_MASK (0xff << 16)
#define VI6_DPR_ROUTE_FXA_SHIFT 16
#define VI6_DPR_ROUTE_FP_MASK (0x3f << 8)
#define VI6_DPR_ROUTE_FP_SHIFT 8
#define VI6_DPR_ROUTE_RT_MASK (0x3f << 0)
#define VI6_DPR_ROUTE_RT_SHIFT 0
#define VI6_DPR_HGO_SMPPT 0x2054
#define VI6_DPR_HGT_SMPPT 0x2058
#define VI6_DPR_SMPPT_TGW_MASK (7 << 8)
#define VI6_DPR_SMPPT_TGW_SHIFT 8
#define VI6_DPR_SMPPT_PT_MASK (0x3f << 0)
#define VI6_DPR_SMPPT_PT_SHIFT 0
#define VI6_DPR_UIF_ROUTE(n) (0x2074 + (n) * 4)
#define VI6_DPR_NODE_RPF(n) (n)
#define VI6_DPR_NODE_UIF(n) (12 + (n))
#define VI6_DPR_NODE_SRU 16
#define VI6_DPR_NODE_UDS(n) (17 + (n))
#define VI6_DPR_NODE_LUT 22
#define VI6_DPR_NODE_BRU_IN(n) (((n) <= 3) ? 23 + (n) : 49)
#define VI6_DPR_NODE_BRU_OUT 27
#define VI6_DPR_NODE_CLU 29
#define VI6_DPR_NODE_HST 30
#define VI6_DPR_NODE_HSI 31
#define VI6_DPR_NODE_BRS_IN(n) (38 + (n))
#define VI6_DPR_NODE_LIF 55 /* Gen2 only */
#define VI6_DPR_NODE_WPF(n) (56 + (n))
#define VI6_DPR_NODE_UNUSED 63
/* -----------------------------------------------------------------------------
* SRU Control Registers
*/
#define VI6_SRU_CTRL0 0x2200
#define VI6_SRU_CTRL0_PARAM0_MASK (0x1ff << 16)
#define VI6_SRU_CTRL0_PARAM0_SHIFT 16
#define VI6_SRU_CTRL0_PARAM1_MASK (0x1f << 8)
#define VI6_SRU_CTRL0_PARAM1_SHIFT 8
#define VI6_SRU_CTRL0_MODE_UPSCALE (4 << 4)
#define VI6_SRU_CTRL0_PARAM2 BIT(3)
#define VI6_SRU_CTRL0_PARAM3 BIT(2)
#define VI6_SRU_CTRL0_PARAM4 BIT(1)
#define VI6_SRU_CTRL0_EN BIT(0)
#define VI6_SRU_CTRL1 0x2204
#define VI6_SRU_CTRL1_PARAM5 0x7ff
#define VI6_SRU_CTRL2 0x2208
#define VI6_SRU_CTRL2_PARAM6_SHIFT 16
#define VI6_SRU_CTRL2_PARAM7_SHIFT 8
#define VI6_SRU_CTRL2_PARAM8_SHIFT 0
/* -----------------------------------------------------------------------------
* UDS Control Registers
*/
#define VI6_UDS_OFFSET 0x100
#define VI6_UDS_CTRL 0x2300
#define VI6_UDS_CTRL_AMD BIT(30)
#define VI6_UDS_CTRL_FMD BIT(29)
#define VI6_UDS_CTRL_BLADV BIT(28)
#define VI6_UDS_CTRL_AON BIT(25)
#define VI6_UDS_CTRL_ATHON BIT(24)
#define VI6_UDS_CTRL_BC BIT(20)
#define VI6_UDS_CTRL_NE_A BIT(19)
#define VI6_UDS_CTRL_NE_RCR BIT(18)
#define VI6_UDS_CTRL_NE_GY BIT(17)
#define VI6_UDS_CTRL_NE_BCB BIT(16)
#define VI6_UDS_CTRL_AMDSLH BIT(2)
#define VI6_UDS_CTRL_TDIPC BIT(1)
#define VI6_UDS_SCALE 0x2304
#define VI6_UDS_SCALE_HMANT_MASK (0xf << 28)
#define VI6_UDS_SCALE_HMANT_SHIFT 28
#define VI6_UDS_SCALE_HFRAC_MASK (0xfff << 16)
#define VI6_UDS_SCALE_HFRAC_SHIFT 16
#define VI6_UDS_SCALE_VMANT_MASK (0xf << 12)
#define VI6_UDS_SCALE_VMANT_SHIFT 12
#define VI6_UDS_SCALE_VFRAC_MASK (0xfff << 0)
#define VI6_UDS_SCALE_VFRAC_SHIFT 0
#define VI6_UDS_ALPTH 0x2308
#define VI6_UDS_ALPTH_TH1_MASK (0xff << 8)
#define VI6_UDS_ALPTH_TH1_SHIFT 8
#define VI6_UDS_ALPTH_TH0_MASK (0xff << 0)
#define VI6_UDS_ALPTH_TH0_SHIFT 0
#define VI6_UDS_ALPVAL 0x230c
#define VI6_UDS_ALPVAL_VAL2_MASK (0xff << 16)
#define VI6_UDS_ALPVAL_VAL2_SHIFT 16
#define VI6_UDS_ALPVAL_VAL1_MASK (0xff << 8)
#define VI6_UDS_ALPVAL_VAL1_SHIFT 8
#define VI6_UDS_ALPVAL_VAL0_MASK (0xff << 0)
#define VI6_UDS_ALPVAL_VAL0_SHIFT 0
#define VI6_UDS_PASS_BWIDTH 0x2310
#define VI6_UDS_PASS_BWIDTH_H_MASK (0x7f << 16)
#define VI6_UDS_PASS_BWIDTH_H_SHIFT 16
#define VI6_UDS_PASS_BWIDTH_V_MASK (0x7f << 0)
#define VI6_UDS_PASS_BWIDTH_V_SHIFT 0
#define VI6_UDS_HPHASE 0x2314
#define VI6_UDS_HPHASE_HSTP_MASK (0xfff << 16)
#define VI6_UDS_HPHASE_HSTP_SHIFT 16
#define VI6_UDS_HPHASE_HEDP_MASK (0xfff << 0)
#define VI6_UDS_HPHASE_HEDP_SHIFT 0
#define VI6_UDS_IPC 0x2318
#define VI6_UDS_IPC_FIELD BIT(27)
#define VI6_UDS_IPC_VEDP_MASK (0xfff << 0)
#define VI6_UDS_IPC_VEDP_SHIFT 0
#define VI6_UDS_HSZCLIP 0x231c
#define VI6_UDS_HSZCLIP_HCEN BIT(28)
#define VI6_UDS_HSZCLIP_HCL_OFST_MASK (0xff << 16)
#define VI6_UDS_HSZCLIP_HCL_OFST_SHIFT 16
#define VI6_UDS_HSZCLIP_HCL_SIZE_MASK (0x1fff << 0)
#define VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT 0
#define VI6_UDS_CLIP_SIZE 0x2324
#define VI6_UDS_CLIP_SIZE_HSIZE_MASK (0x1fff << 16)
#define VI6_UDS_CLIP_SIZE_HSIZE_SHIFT 16
#define VI6_UDS_CLIP_SIZE_VSIZE_MASK (0x1fff << 0)
#define VI6_UDS_CLIP_SIZE_VSIZE_SHIFT 0
#define VI6_UDS_FILL_COLOR 0x2328
#define VI6_UDS_FILL_COLOR_RFILC_MASK (0xff << 16)
#define VI6_UDS_FILL_COLOR_RFILC_SHIFT 16
#define VI6_UDS_FILL_COLOR_GFILC_MASK (0xff << 8)
#define VI6_UDS_FILL_COLOR_GFILC_SHIFT 8
#define VI6_UDS_FILL_COLOR_BFILC_MASK (0xff << 0)
#define VI6_UDS_FILL_COLOR_BFILC_SHIFT 0
/* -----------------------------------------------------------------------------
* LUT Control Registers
*/
#define VI6_LUT_CTRL 0x2800
#define VI6_LUT_CTRL_EN BIT(0)
/* -----------------------------------------------------------------------------
* CLU Control Registers
*/
#define VI6_CLU_CTRL 0x2900
#define VI6_CLU_CTRL_AAI BIT(28)
#define VI6_CLU_CTRL_MVS BIT(24)
#define VI6_CLU_CTRL_AX1I_2D (3 << 14)
#define VI6_CLU_CTRL_AX2I_2D (1 << 12)
#define VI6_CLU_CTRL_OS0_2D (3 << 8)
#define VI6_CLU_CTRL_OS1_2D (1 << 6)
#define VI6_CLU_CTRL_OS2_2D (3 << 4)
#define VI6_CLU_CTRL_M2D BIT(1)
#define VI6_CLU_CTRL_EN BIT(0)
/* -----------------------------------------------------------------------------
* HST Control Registers
*/
#define VI6_HST_CTRL 0x2a00
#define VI6_HST_CTRL_EN BIT(0)
/* -----------------------------------------------------------------------------
* HSI Control Registers
*/
#define VI6_HSI_CTRL 0x2b00
#define VI6_HSI_CTRL_EN BIT(0)
/* -----------------------------------------------------------------------------
* BRS and BRU Control Registers
*/
#define VI6_ROP_NOP 0
#define VI6_ROP_AND 1
#define VI6_ROP_AND_REV 2
#define VI6_ROP_COPY 3
#define VI6_ROP_AND_INV 4
#define VI6_ROP_CLEAR 5
#define VI6_ROP_XOR 6
#define VI6_ROP_OR 7
#define VI6_ROP_NOR 8
#define VI6_ROP_EQUIV 9
#define VI6_ROP_INVERT 10
#define VI6_ROP_OR_REV 11
#define VI6_ROP_COPY_INV 12
#define VI6_ROP_OR_INV 13
#define VI6_ROP_NAND 14
#define VI6_ROP_SET 15
#define VI6_BRU_BASE 0x2c00
#define VI6_BRS_BASE 0x3900
#define VI6_BRU_INCTRL 0x0000
#define VI6_BRU_INCTRL_NRM BIT(28)
#define VI6_BRU_INCTRL_DnON (1 << (16 + (n)))
#define VI6_BRU_INCTRL_DITHn_OFF (0 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_18BPP (1 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_16BPP (2 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_15BPP (3 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_12BPP (4 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_8BPP (5 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_MASK (7 << ((n) * 4))
#define VI6_BRU_INCTRL_DITHn_SHIFT ((n) * 4)
#define VI6_BRU_VIRRPF_SIZE 0x0004
#define VI6_BRU_VIRRPF_SIZE_HSIZE_MASK (0x1fff << 16)
#define VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT 16
#define VI6_BRU_VIRRPF_SIZE_VSIZE_MASK (0x1fff << 0)
#define VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT 0
#define VI6_BRU_VIRRPF_LOC 0x0008
#define VI6_BRU_VIRRPF_LOC_HCOORD_MASK (0x1fff << 16)
#define VI6_BRU_VIRRPF_LOC_HCOORD_SHIFT 16
#define VI6_BRU_VIRRPF_LOC_VCOORD_MASK (0x1fff << 0)
#define VI6_BRU_VIRRPF_LOC_VCOORD_SHIFT 0
#define VI6_BRU_VIRRPF_COL 0x000c
#define VI6_BRU_VIRRPF_COL_A_MASK (0xff << 24)
#define VI6_BRU_VIRRPF_COL_A_SHIFT 24
#define VI6_BRU_VIRRPF_COL_RCR_MASK (0xff << 16)
#define VI6_BRU_VIRRPF_COL_RCR_SHIFT 16
#define VI6_BRU_VIRRPF_COL_GY_MASK (0xff << 8)
#define VI6_BRU_VIRRPF_COL_GY_SHIFT 8
#define VI6_BRU_VIRRPF_COL_BCB_MASK (0xff << 0)
#define VI6_BRU_VIRRPF_COL_BCB_SHIFT 0
#define VI6_BRU_CTRL(n) (0x0010 + (n) * 8 + ((n) <= 3 ? 0 : 4))
#define VI6_BRU_CTRL_RBC BIT(31)
#define VI6_BRU_CTRL_DSTSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 20)
#define VI6_BRU_CTRL_DSTSEL_VRPF (4 << 20)
#define VI6_BRU_CTRL_DSTSEL_MASK (7 << 20)
#define VI6_BRU_CTRL_SRCSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 16)
#define VI6_BRU_CTRL_SRCSEL_VRPF (4 << 16)
#define VI6_BRU_CTRL_SRCSEL_MASK (7 << 16)
#define VI6_BRU_CTRL_CROP(rop) ((rop) << 4)
#define VI6_BRU_CTRL_CROP_MASK (0xf << 4)
#define VI6_BRU_CTRL_AROP(rop) ((rop) << 0)
#define VI6_BRU_CTRL_AROP_MASK (0xf << 0)
#define VI6_BRU_BLD(n) (0x0014 + (n) * 8 + ((n) <= 3 ? 0 : 4))
#define VI6_BRU_BLD_CBES BIT(31)
#define VI6_BRU_BLD_CCMDX_DST_A (0 << 28)
#define VI6_BRU_BLD_CCMDX_255_DST_A (1 << 28)
#define VI6_BRU_BLD_CCMDX_SRC_A (2 << 28)
#define VI6_BRU_BLD_CCMDX_255_SRC_A (3 << 28)
#define VI6_BRU_BLD_CCMDX_COEFX (4 << 28)
#define VI6_BRU_BLD_CCMDX_MASK (7 << 28)
#define VI6_BRU_BLD_CCMDY_DST_A (0 << 24)
#define VI6_BRU_BLD_CCMDY_255_DST_A (1 << 24)
#define VI6_BRU_BLD_CCMDY_SRC_A (2 << 24)
#define VI6_BRU_BLD_CCMDY_255_SRC_A (3 << 24)
#define VI6_BRU_BLD_CCMDY_COEFY (4 << 24)
#define VI6_BRU_BLD_CCMDY_MASK (7 << 24)
#define VI6_BRU_BLD_CCMDY_SHIFT 24
#define VI6_BRU_BLD_ABES BIT(23)
#define VI6_BRU_BLD_ACMDX_DST_A (0 << 20)
#define VI6_BRU_BLD_ACMDX_255_DST_A (1 << 20)
#define VI6_BRU_BLD_ACMDX_SRC_A (2 << 20)
#define VI6_BRU_BLD_ACMDX_255_SRC_A (3 << 20)
#define VI6_BRU_BLD_ACMDX_COEFX (4 << 20)
#define VI6_BRU_BLD_ACMDX_MASK (7 << 20)
#define VI6_BRU_BLD_ACMDY_DST_A (0 << 16)
#define VI6_BRU_BLD_ACMDY_255_DST_A (1 << 16)
#define VI6_BRU_BLD_ACMDY_SRC_A (2 << 16)
#define VI6_BRU_BLD_ACMDY_255_SRC_A (3 << 16)
#define VI6_BRU_BLD_ACMDY_COEFY (4 << 16)
#define VI6_BRU_BLD_ACMDY_MASK (7 << 16)
#define VI6_BRU_BLD_COEFX_MASK (0xff << 8)
#define VI6_BRU_BLD_COEFX_SHIFT 8
#define VI6_BRU_BLD_COEFY_MASK (0xff << 0)
#define VI6_BRU_BLD_COEFY_SHIFT 0
#define VI6_BRU_ROP 0x0030 /* Only available on BRU */
#define VI6_BRU_ROP_DSTSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 20)
#define VI6_BRU_ROP_DSTSEL_VRPF (4 << 20)
#define VI6_BRU_ROP_DSTSEL_MASK (7 << 20)
#define VI6_BRU_ROP_CROP(rop) ((rop) << 4)
#define VI6_BRU_ROP_CROP_MASK (0xf << 4)
#define VI6_BRU_ROP_AROP(rop) ((rop) << 0)
#define VI6_BRU_ROP_AROP_MASK (0xf << 0)
/* -----------------------------------------------------------------------------
* HGO Control Registers
*/
#define VI6_HGO_OFFSET 0x3000
#define VI6_HGO_OFFSET_HOFFSET_SHIFT 16
#define VI6_HGO_OFFSET_VOFFSET_SHIFT 0
#define VI6_HGO_SIZE 0x3004
#define VI6_HGO_SIZE_HSIZE_SHIFT 16
#define VI6_HGO_SIZE_VSIZE_SHIFT 0
#define VI6_HGO_MODE 0x3008
#define VI6_HGO_MODE_STEP BIT(10)
#define VI6_HGO_MODE_MAXRGB BIT(7)
#define VI6_HGO_MODE_OFSB_R BIT(6)
#define VI6_HGO_MODE_OFSB_G BIT(5)
#define VI6_HGO_MODE_OFSB_B BIT(4)
#define VI6_HGO_MODE_HRATIO_SHIFT 2
#define VI6_HGO_MODE_VRATIO_SHIFT 0
#define VI6_HGO_LB_TH 0x300c
#define VI6_HGO_LBn_H(n) (0x3010 + (n) * 8)
#define VI6_HGO_LBn_V(n) (0x3014 + (n) * 8)
#define VI6_HGO_R_HISTO(n) (0x3030 + (n) * 4)
#define VI6_HGO_R_MAXMIN 0x3130
#define VI6_HGO_R_SUM 0x3134
#define VI6_HGO_R_LB_DET 0x3138
#define VI6_HGO_G_HISTO(n) (0x3140 + (n) * 4)
#define VI6_HGO_G_MAXMIN 0x3240
#define VI6_HGO_G_SUM 0x3244
#define VI6_HGO_G_LB_DET 0x3248
#define VI6_HGO_B_HISTO(n) (0x3250 + (n) * 4)
#define VI6_HGO_B_MAXMIN 0x3350
#define VI6_HGO_B_SUM 0x3354
#define VI6_HGO_B_LB_DET 0x3358
#define VI6_HGO_EXT_HIST_ADDR 0x335c
#define VI6_HGO_EXT_HIST_DATA 0x3360
#define VI6_HGO_REGRST 0x33fc
#define VI6_HGO_REGRST_RCLEA BIT(0)
/* -----------------------------------------------------------------------------
* HGT Control Registers
*/
#define VI6_HGT_OFFSET 0x3400
#define VI6_HGT_OFFSET_HOFFSET_SHIFT 16
#define VI6_HGT_OFFSET_VOFFSET_SHIFT 0
#define VI6_HGT_SIZE 0x3404
#define VI6_HGT_SIZE_HSIZE_SHIFT 16
#define VI6_HGT_SIZE_VSIZE_SHIFT 0
#define VI6_HGT_MODE 0x3408
#define VI6_HGT_MODE_HRATIO_SHIFT 2
#define VI6_HGT_MODE_VRATIO_SHIFT 0
#define VI6_HGT_HUE_AREA(n) (0x340c + (n) * 4)
#define VI6_HGT_HUE_AREA_LOWER_SHIFT 16
#define VI6_HGT_HUE_AREA_UPPER_SHIFT 0
#define VI6_HGT_LB_TH 0x3424
#define VI6_HGT_LBn_H(n) (0x3428 + (n) * 8)
#define VI6_HGT_LBn_V(n) (0x342c + (n) * 8)
#define VI6_HGT_HISTO(m, n) (0x3450 + (m) * 128 + (n) * 4)
#define VI6_HGT_MAXMIN 0x3750
#define VI6_HGT_SUM 0x3754
#define VI6_HGT_LB_DET 0x3758
#define VI6_HGT_REGRST 0x37fc
#define VI6_HGT_REGRST_RCLEA BIT(0)
/* -----------------------------------------------------------------------------
* LIF Control Registers
*/
#define VI6_LIF_OFFSET (-0x100)
#define VI6_LIF_CTRL 0x3b00
#define VI6_LIF_CTRL_OBTH_MASK (0x7ff << 16)
#define VI6_LIF_CTRL_OBTH_SHIFT 16
#define VI6_LIF_CTRL_CFMT BIT(4)
#define VI6_LIF_CTRL_REQSEL BIT(1)
#define VI6_LIF_CTRL_LIF_EN BIT(0)
#define VI6_LIF_CSBTH 0x3b04
#define VI6_LIF_CSBTH_HBTH_MASK (0x7ff << 16)
#define VI6_LIF_CSBTH_HBTH_SHIFT 16
#define VI6_LIF_CSBTH_LBTH_MASK (0x7ff << 0)
#define VI6_LIF_CSBTH_LBTH_SHIFT 0
#define VI6_LIF_LBA 0x3b0c
#define VI6_LIF_LBA_LBA0 BIT(31)
#define VI6_LIF_LBA_LBA1_MASK (0xfff << 16)
#define VI6_LIF_LBA_LBA1_SHIFT 16
/* -----------------------------------------------------------------------------
* Security Control Registers
*/
#define VI6_SECURITY_CTRL0 0x3d00
#define VI6_SECURITY_CTRL1 0x3d04
/* -----------------------------------------------------------------------------
* IP Version Registers
*/
#define VI6_IP_VERSION 0x3f00
#define VI6_IP_VERSION_MASK (0xffff << 0)
#define VI6_IP_VERSION_MODEL_MASK (0xff << 8)
#define VI6_IP_VERSION_MODEL_VSPS_H2 (0x09 << 8)
#define VI6_IP_VERSION_MODEL_VSPR_H2 (0x0a << 8)
#define VI6_IP_VERSION_MODEL_VSPD_GEN2 (0x0b << 8)
#define VI6_IP_VERSION_MODEL_VSPS_M2 (0x0c << 8)
#define VI6_IP_VERSION_MODEL_VSPS_V2H (0x12 << 8)
#define VI6_IP_VERSION_MODEL_VSPD_V2H (0x13 << 8)
#define VI6_IP_VERSION_MODEL_VSPI_GEN3 (0x14 << 8)
#define VI6_IP_VERSION_MODEL_VSPBD_GEN3 (0x15 << 8)
#define VI6_IP_VERSION_MODEL_VSPBC_GEN3 (0x16 << 8)
#define VI6_IP_VERSION_MODEL_VSPD_GEN3 (0x17 << 8)
#define VI6_IP_VERSION_MODEL_VSPD_V3 (0x18 << 8)
#define VI6_IP_VERSION_MODEL_VSPDL_GEN3 (0x19 << 8)
#define VI6_IP_VERSION_MODEL_VSPBS_GEN3 (0x1a << 8)
#define VI6_IP_VERSION_MODEL_VSPD_GEN4 (0x1c << 8)
#define VI6_IP_VERSION_MODEL_VSPX_GEN4 (0x1d << 8)
/* RZ/G2L SoCs have no version register, So use 0x80 as the model version */
#define VI6_IP_VERSION_MODEL_VSPD_RZG2L (0x80 << 8)
#define VI6_IP_VERSION_SOC_MASK (0xff << 0)
#define VI6_IP_VERSION_SOC_H2 (0x01 << 0)
#define VI6_IP_VERSION_SOC_V2H (0x01 << 0)
#define VI6_IP_VERSION_SOC_V3M (0x01 << 0)
#define VI6_IP_VERSION_SOC_M2 (0x02 << 0)
#define VI6_IP_VERSION_SOC_M3W (0x02 << 0)
#define VI6_IP_VERSION_SOC_V3H (0x02 << 0)
#define VI6_IP_VERSION_SOC_H3 (0x03 << 0)
#define VI6_IP_VERSION_SOC_D3 (0x04 << 0)
#define VI6_IP_VERSION_SOC_M3N (0x04 << 0)
#define VI6_IP_VERSION_SOC_E3 (0x04 << 0)
#define VI6_IP_VERSION_SOC_V3U (0x05 << 0)
#define VI6_IP_VERSION_SOC_V4H (0x06 << 0)
/* RZ/G2L SoCs have no version register, So use 0x80 for SoC Identification */
#define VI6_IP_VERSION_SOC_RZG2L (0x80 << 0)
#define VI6_IP_VERSION_VSP_SW (0xfffe << 16) /* SW VSP version */
/* -----------------------------------------------------------------------------
* RPF CLUT Registers
*/
#define VI6_CLUT_TABLE 0x4000
/* -----------------------------------------------------------------------------
* 1D LUT Registers
*/
#define VI6_LUT_TABLE 0x7000
/* -----------------------------------------------------------------------------
* 3D LUT Registers
*/
#define VI6_CLU_ADDR 0x7400
#define VI6_CLU_DATA 0x7404
/* -----------------------------------------------------------------------------
* Formats
*/
#define VI6_FMT_RGB_332 0x00
#define VI6_FMT_XRGB_4444 0x01
#define VI6_FMT_RGBX_4444 0x02
#define VI6_FMT_XRGB_1555 0x04
#define VI6_FMT_RGBX_5551 0x05
#define VI6_FMT_RGB_565 0x06
#define VI6_FMT_AXRGB_86666 0x07
#define VI6_FMT_RGBXA_66668 0x08
#define VI6_FMT_XRGBA_66668 0x09
#define VI6_FMT_ARGBX_86666 0x0a
#define VI6_FMT_AXRXGXB_8262626 0x0b
#define VI6_FMT_XRXGXBA_2626268 0x0c
#define VI6_FMT_ARXGXBX_8626262 0x0d
#define VI6_FMT_RXGXBXA_6262628 0x0e
#define VI6_FMT_XRGB_6666 0x0f
#define VI6_FMT_RGBX_6666 0x10
#define VI6_FMT_XRXGXB_262626 0x11
#define VI6_FMT_RXGXBX_626262 0x12
#define VI6_FMT_ARGB_8888 0x13
#define VI6_FMT_RGBA_8888 0x14
#define VI6_FMT_RGB_888 0x15
#define VI6_FMT_XRGXGB_763763 0x16
#define VI6_FMT_XXRGB_86666 0x17
#define VI6_FMT_BGR_888 0x18
#define VI6_FMT_ARGB_4444 0x19
#define VI6_FMT_RGBA_4444 0x1a
#define VI6_FMT_ARGB_1555 0x1b
#define VI6_FMT_RGBA_5551 0x1c
#define VI6_FMT_ABGR_4444 0x1d
#define VI6_FMT_BGRA_4444 0x1e
#define VI6_FMT_ABGR_1555 0x1f
#define VI6_FMT_BGRA_5551 0x20
#define VI6_FMT_XBXGXR_262626 0x21
#define VI6_FMT_ABGR_8888 0x22
#define VI6_FMT_XXRGB_88565 0x23
#define VI6_FMT_RGB10_RGB10A2_A2RGB10 0x30
#define VI6_FMT_Y_UV_444 0x40
#define VI6_FMT_Y_UV_422 0x41
#define VI6_FMT_Y_UV_420 0x42
#define VI6_FMT_YUV_444 0x46
#define VI6_FMT_YUYV_422 0x47
#define VI6_FMT_YYUV_422 0x48
#define VI6_FMT_YUV_420 0x49
#define VI6_FMT_Y_U_V_444 0x4a
#define VI6_FMT_Y_U_V_422 0x4b
#define VI6_FMT_Y_U_V_420 0x4c
#endif /* __VSP1_REGS_H__ */
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
#include "vsp1_video.h"
#define RPF_MAX_WIDTH 8190
#define RPF_MAX_HEIGHT 8190
/* Pre extended display list command data structure. */
struct vsp1_extcmd_auto_fld_body {
u32 top_y0;
u32 bottom_y0;
u32 top_c0;
u32 bottom_c0;
u32 top_c1;
u32 bottom_c1;
u32 reserved0;
u32 reserved1;
} __packed;
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_rpf_write(struct vsp1_rwpf *rpf,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg + rpf->entity.index * VI6_RPF_OFFSET,
data);
}
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void rpf_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev);
const struct vsp1_format_info *fmtinfo = rpf->fmtinfo;
const struct v4l2_pix_format_mplane *format = &rpf->format;
const struct v4l2_mbus_framefmt *source_format;
const struct v4l2_mbus_framefmt *sink_format;
unsigned int left = 0;
unsigned int top = 0;
u32 pstride;
u32 infmt;
/* Stride */
pstride = format->plane_fmt[0].bytesperline
<< VI6_RPF_SRCM_PSTRIDE_Y_SHIFT;
if (format->num_planes > 1)
pstride |= format->plane_fmt[1].bytesperline
<< VI6_RPF_SRCM_PSTRIDE_C_SHIFT;
/*
* pstride has both STRIDE_Y and STRIDE_C, but multiplying the whole
* of pstride by 2 is conveniently OK here as we are multiplying both
* values.
*/
if (pipe->interlaced)
pstride *= 2;
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_PSTRIDE, pstride);
/* Format */
sink_format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
source_format = v4l2_subdev_state_get_format(state, RWPF_PAD_SOURCE);
infmt = (pipe->iif ? 0 : VI6_RPF_INFMT_CIPM)
| (fmtinfo->hwfmt << VI6_RPF_INFMT_RDFMT_SHIFT);
if (fmtinfo->swap_yc)
infmt |= VI6_RPF_INFMT_SPYCS;
if (fmtinfo->swap_uv)
infmt |= VI6_RPF_INFMT_SPUVS;
if (sink_format->code != source_format->code) {
u16 ycbcr_enc;
u16 quantization;
u32 rdtm;
if (sink_format->code == MEDIA_BUS_FMT_AYUV8_1X32) {
ycbcr_enc = sink_format->ycbcr_enc;
quantization = sink_format->quantization;
} else {
ycbcr_enc = source_format->ycbcr_enc;
quantization = source_format->quantization;
}
if (ycbcr_enc == V4L2_YCBCR_ENC_601 &&
quantization == V4L2_QUANTIZATION_LIM_RANGE)
rdtm = VI6_RPF_INFMT_RDTM_BT601;
else if (ycbcr_enc == V4L2_YCBCR_ENC_601 &&
quantization == V4L2_QUANTIZATION_FULL_RANGE)
rdtm = VI6_RPF_INFMT_RDTM_BT601_EXT;
else if (ycbcr_enc == V4L2_YCBCR_ENC_709 &&
quantization == V4L2_QUANTIZATION_LIM_RANGE)
rdtm = VI6_RPF_INFMT_RDTM_BT709;
else
rdtm = VI6_RPF_INFMT_RDTM_BT709_EXT;
infmt |= VI6_RPF_INFMT_CSC | rdtm;
}
vsp1_rpf_write(rpf, dlb, VI6_RPF_INFMT, infmt);
vsp1_rpf_write(rpf, dlb, VI6_RPF_DSWAP, fmtinfo->swap);
/* No further configuration for VSPX. */
if (pipe->iif) {
/* VSPX wants alpha_sel to be set to 0. */
vsp1_rpf_write(rpf, dlb, VI6_RPF_ALPH_SEL, 0);
return;
}
if (entity->vsp1->info->gen == 4) {
u32 ext_infmt0;
u32 ext_infmt1;
u32 ext_infmt2;
switch (fmtinfo->fourcc) {
case V4L2_PIX_FMT_RGBX1010102:
ext_infmt0 = VI6_RPF_EXT_INFMT0_BYPP_M1_RGB10;
ext_infmt1 = VI6_RPF_EXT_INFMT1_PACK_CPOS(0, 10, 20, 0);
ext_infmt2 = VI6_RPF_EXT_INFMT2_PACK_CLEN(10, 10, 10, 0);
break;
case V4L2_PIX_FMT_RGBA1010102:
ext_infmt0 = VI6_RPF_EXT_INFMT0_BYPP_M1_RGB10;
ext_infmt1 = VI6_RPF_EXT_INFMT1_PACK_CPOS(0, 10, 20, 30);
ext_infmt2 = VI6_RPF_EXT_INFMT2_PACK_CLEN(10, 10, 10, 2);
break;
case V4L2_PIX_FMT_ARGB2101010:
ext_infmt0 = VI6_RPF_EXT_INFMT0_BYPP_M1_RGB10;
ext_infmt1 = VI6_RPF_EXT_INFMT1_PACK_CPOS(2, 12, 22, 0);
ext_infmt2 = VI6_RPF_EXT_INFMT2_PACK_CLEN(10, 10, 10, 2);
break;
case V4L2_PIX_FMT_Y210:
ext_infmt0 = VI6_RPF_EXT_INFMT0_F2B |
VI6_RPF_EXT_INFMT0_IPBD_Y_10 |
VI6_RPF_EXT_INFMT0_IPBD_C_10;
ext_infmt1 = 0x0;
ext_infmt2 = 0x0;
break;
case V4L2_PIX_FMT_Y212:
ext_infmt0 = VI6_RPF_EXT_INFMT0_F2B |
VI6_RPF_EXT_INFMT0_IPBD_Y_12 |
VI6_RPF_EXT_INFMT0_IPBD_C_12;
ext_infmt1 = 0x0;
ext_infmt2 = 0x0;
break;
default:
ext_infmt0 = 0;
ext_infmt1 = 0;
ext_infmt2 = 0;
break;
}
vsp1_rpf_write(rpf, dlb, VI6_RPF_EXT_INFMT0, ext_infmt0);
vsp1_rpf_write(rpf, dlb, VI6_RPF_EXT_INFMT1, ext_infmt1);
vsp1_rpf_write(rpf, dlb, VI6_RPF_EXT_INFMT2, ext_infmt2);
}
/* Output location. */
if (pipe->brx) {
const struct v4l2_rect *compose;
compose = v4l2_subdev_state_get_compose(pipe->brx->state,
rpf->brx_input);
left = compose->left;
top = compose->top;
}
if (pipe->interlaced)
top /= 2;
vsp1_rpf_write(rpf, dlb, VI6_RPF_LOC,
(left << VI6_RPF_LOC_HCOORD_SHIFT) |
(top << VI6_RPF_LOC_VCOORD_SHIFT));
/*
* On Gen2 use the alpha channel (extended to 8 bits) when available or
* a fixed alpha value set through the V4L2_CID_ALPHA_COMPONENT control
* otherwise.
*
* The Gen3+ RPF has extended alpha capability and can both multiply the
* alpha channel by a fixed global alpha value, and multiply the pixel
* components to convert the input to premultiplied alpha.
*
* As alpha premultiplication is available in the BRx for both Gen2 and
* Gen3+ we handle it there and use the Gen3 alpha multiplier for global
* alpha multiplication only. This however prevents conversion to
* premultiplied alpha if no BRx is present in the pipeline. If that use
* case turns out to be useful we will revisit the implementation (for
* Gen3 only).
*
* We enable alpha multiplication on Gen3+ using the fixed alpha value
* set through the V4L2_CID_ALPHA_COMPONENT control when the input
* contains an alpha channel. On Gen2 the global alpha is ignored in
* that case.
*
* In all cases, disable color keying.
*/
vsp1_rpf_write(rpf, dlb, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_AEXT_EXT |
(fmtinfo->alpha ? VI6_RPF_ALPH_SEL_ASEL_PACKED
: VI6_RPF_ALPH_SEL_ASEL_FIXED));
if (entity->vsp1->info->gen >= 3) {
u32 mult;
if (fmtinfo->alpha) {
/*
* When the input contains an alpha channel enable the
* alpha multiplier. If the input is premultiplied we
* need to multiply both the alpha channel and the pixel
* components by the global alpha value to keep them
* premultiplied. Otherwise multiply the alpha channel
* only.
*/
bool premultiplied = format->flags
& V4L2_PIX_FMT_FLAG_PREMUL_ALPHA;
mult = VI6_RPF_MULT_ALPHA_A_MMD_RATIO
| (premultiplied ?
VI6_RPF_MULT_ALPHA_P_MMD_RATIO :
VI6_RPF_MULT_ALPHA_P_MMD_NONE);
} else {
/*
* When the input doesn't contain an alpha channel the
* global alpha value is applied in the unpacking unit,
* the alpha multiplier isn't needed and must be
* disabled.
*/
mult = VI6_RPF_MULT_ALPHA_A_MMD_NONE
| VI6_RPF_MULT_ALPHA_P_MMD_NONE;
}
rpf->mult_alpha = mult;
}
vsp1_rpf_write(rpf, dlb, VI6_RPF_MSK_CTRL, 0);
vsp1_rpf_write(rpf, dlb, VI6_RPF_CKEY_CTRL, 0);
}
static void vsp1_rpf_configure_autofld(struct vsp1_rwpf *rpf,
struct vsp1_dl_list *dl)
{
const struct v4l2_pix_format_mplane *format = &rpf->format;
struct vsp1_dl_ext_cmd *cmd;
struct vsp1_extcmd_auto_fld_body *auto_fld;
u32 offset_y, offset_c;
cmd = vsp1_dl_get_pre_cmd(dl);
if (WARN_ONCE(!cmd, "Failed to obtain an autofld cmd"))
return;
/* Re-index our auto_fld to match the current RPF. */
auto_fld = cmd->data;
auto_fld = &auto_fld[rpf->entity.index];
auto_fld->top_y0 = rpf->mem.addr[0];
auto_fld->top_c0 = rpf->mem.addr[1];
auto_fld->top_c1 = rpf->mem.addr[2];
offset_y = format->plane_fmt[0].bytesperline;
offset_c = format->plane_fmt[1].bytesperline;
auto_fld->bottom_y0 = rpf->mem.addr[0] + offset_y;
auto_fld->bottom_c0 = rpf->mem.addr[1] + offset_c;
auto_fld->bottom_c1 = rpf->mem.addr[2] + offset_c;
cmd->flags |= VI6_DL_EXT_AUTOFLD_INT | BIT(16 + rpf->entity.index);
}
static void rpf_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev);
vsp1_rpf_write(rpf, dlb, VI6_RPF_VRTCOL_SET,
rpf->alpha << VI6_RPF_VRTCOL_SET_LAYA_SHIFT);
vsp1_rpf_write(rpf, dlb, VI6_RPF_MULT_ALPHA, rpf->mult_alpha |
(rpf->alpha << VI6_RPF_MULT_ALPHA_RATIO_SHIFT));
vsp1_pipeline_propagate_alpha(pipe, dlb, rpf->alpha);
}
static void rpf_configure_partition(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev);
struct vsp1_rwpf_memory mem = rpf->mem;
struct vsp1_device *vsp1 = rpf->entity.vsp1;
const struct vsp1_format_info *fmtinfo = rpf->fmtinfo;
const struct v4l2_pix_format_mplane *format = &rpf->format;
struct v4l2_rect crop = partition->rpf[rpf->entity.index];
/*
* Source size and crop offsets.
*
* The crop offsets correspond to the location of the crop
* rectangle top left corner in the plane buffer. Only two
* offsets are needed, as planes 2 and 3 always have identical
* strides.
*/
if (pipe->interlaced) {
crop.height = round_down(crop.height / 2, fmtinfo->vsub);
crop.top = round_down(crop.top / 2, fmtinfo->vsub);
}
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_BSIZE,
(crop.width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) |
(crop.height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT));
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_ESIZE,
(crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) |
(crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT));
mem.addr[0] += crop.top * format->plane_fmt[0].bytesperline
+ crop.left * fmtinfo->bpp[0] / 8;
if (format->num_planes > 1) {
unsigned int bpl = format->plane_fmt[1].bytesperline;
unsigned int offset;
offset = crop.top / fmtinfo->vsub * bpl
+ crop.left / fmtinfo->hsub * fmtinfo->bpp[1] / 8;
mem.addr[1] += offset;
mem.addr[2] += offset;
}
/*
* On Gen3+ hardware the SPUVS bit has no effect on 3-planar
* formats. Swap the U and V planes manually in that case.
*/
if (vsp1->info->gen >= 3 && format->num_planes == 3 &&
fmtinfo->swap_uv)
swap(mem.addr[1], mem.addr[2]);
/*
* Interlaced pipelines will use the extended pre-cmd to process
* SRCM_ADDR_{Y,C0,C1}.
*/
if (pipe->interlaced) {
vsp1_rpf_configure_autofld(rpf, dl);
} else {
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_Y, mem.addr[0]);
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C0, mem.addr[1]);
vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C1, mem.addr[2]);
}
}
static void rpf_partition(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int partition_idx,
struct v4l2_rect *window)
{
struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev);
struct v4l2_rect *rpf_rect = &partition->rpf[rpf->entity.index];
/*
* Partition Algorithm Control
*
* The partition algorithm can split this frame into multiple slices. We
* must adjust our partition window based on the pipe configuration to
* match the destination partition window. To achieve this, we adjust
* our crop to provide a 'sub-crop' matching the expected partition
* window.
*/
*rpf_rect = *v4l2_subdev_state_get_crop(state, RWPF_PAD_SINK);
if (pipe->partitions > 1) {
rpf_rect->width = window->width;
rpf_rect->left += window->left;
}
}
static const struct vsp1_entity_operations rpf_entity_ops = {
.configure_stream = rpf_configure_stream,
.configure_frame = rpf_configure_frame,
.configure_partition = rpf_configure_partition,
.partition = rpf_partition,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index)
{
struct vsp1_rwpf *rpf;
char name[6];
int ret;
rpf = devm_kzalloc(vsp1->dev, sizeof(*rpf), GFP_KERNEL);
if (rpf == NULL)
return ERR_PTR(-ENOMEM);
rpf->entity.ops = &rpf_entity_ops;
rpf->entity.type = VSP1_ENTITY_RPF;
rpf->entity.index = index;
rpf->entity.min_width = RWPF_MIN_WIDTH;
rpf->entity.min_height = RWPF_MIN_HEIGHT;
rpf->entity.max_width = RPF_MAX_WIDTH;
rpf->entity.max_height = RPF_MAX_HEIGHT;
sprintf(name, "rpf.%u", index);
ret = vsp1_entity_init(vsp1, &rpf->entity, name, 2, &vsp1_rwpf_subdev_ops,
MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER);
if (ret < 0)
return ERR_PTR(ret);
/* Initialize the control handler. */
ret = vsp1_rwpf_init_ctrls(rpf, 0);
if (ret < 0) {
dev_err(vsp1->dev, "rpf%u: failed to initialize controls\n",
index);
goto error;
}
v4l2_ctrl_handler_setup(&rpf->ctrls);
return rpf;
error:
vsp1_entity_destroy(&rpf->entity);
return ERR_PTR(ret);
}
@@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_rwpf.c -- R-Car VSP1 Read and Write Pixel Formatters
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_entity.h"
#include "vsp1_rwpf.h"
#include "vsp1_video.h"
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
/* Keep HSV last. */
static const unsigned int rwpf_codes[] = {
MEDIA_BUS_FMT_AYUV8_1X32,
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
};
static int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
if (code->pad == RWPF_PAD_SINK)
return vsp1_subdev_enum_mbus_code(subdev, sd_state, code);
state = vsp1_entity_get_state(entity, sd_state, code->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
guard(mutex)(&entity->lock);
/*
* The RWPF supports conversion between RGB and YUV formats, but HSV
* formats can't be converted.
*/
if (format->code == MEDIA_BUS_FMT_AHSV8888_1X32) {
if (code->index)
return -EINVAL;
code->code = MEDIA_BUS_FMT_AHSV8888_1X32;
} else {
if (code->index >= ARRAY_SIZE(rwpf_codes) - 1)
return -EINVAL;
code->code = rwpf_codes[code->index];
}
if (code->code == MEDIA_BUS_FMT_AYUV8_1X32)
code->flags = V4L2_SUBDEV_MBUS_CODE_CSC_YCBCR_ENC
| V4L2_SUBDEV_MBUS_CODE_CSC_QUANTIZATION;
return 0;
}
static int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct vsp1_entity *entity = to_vsp1_entity(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
if (fse->pad == RWPF_PAD_SINK)
return vsp1_subdev_enum_frame_size(subdev, sd_state, fse);
if (fse->index)
return -EINVAL;
state = vsp1_entity_get_state(entity, sd_state, fse->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
guard(mutex)(&entity->lock);
/*
* The RWPF supports conversion between RGB and YUV formats, but
* HSV formats can't be converted.
*/
if ((format->code == MEDIA_BUS_FMT_AHSV8888_1X32) !=
(fse->code == MEDIA_BUS_FMT_AHSV8888_1X32))
return -EINVAL;
/*
* The size on the source pad is fixed and always identical to
* the sink pad.
*/
fse->min_width = format->width;
fse->max_width = format->width;
fse->min_height = format->height;
fse->max_height = format->height;
return 0;
}
static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_rwpf *rwpf = to_rwpf(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
guard(mutex)(&rwpf->entity.lock);
state = vsp1_entity_get_state(&rwpf->entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
/* Default to YUV if the requested format is not supported. */
if (fmt->format.code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
fmt->format.code != MEDIA_BUS_FMT_AHSV8888_1X32 &&
fmt->format.code != MEDIA_BUS_FMT_AYUV8_1X32)
fmt->format.code = MEDIA_BUS_FMT_AYUV8_1X32;
format = v4l2_subdev_state_get_format(state, fmt->pad);
if (fmt->pad == RWPF_PAD_SOURCE) {
const struct v4l2_mbus_framefmt *sink_format =
v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
u16 flags = fmt->format.flags & V4L2_MBUS_FRAMEFMT_SET_CSC;
bool csc;
/*
* The RWPF performs format conversion but can't scale, only the
* format code, encoding and quantization can be changed on the
* source pad when converting between RGB and YUV.
*/
if (sink_format->code != MEDIA_BUS_FMT_AHSV8888_1X32 &&
fmt->format.code != MEDIA_BUS_FMT_AHSV8888_1X32)
format->code = fmt->format.code;
else
format->code = sink_format->code;
/*
* Encoding and quantization can only be configured when YCbCr
* <-> RGB is enabled. The V4L2 API requires userspace to set
* the V4L2_MBUS_FRAMEFMT_SET_CSC flag. If either of these
* conditions is not met, use the encoding and quantization
* values from the sink pad.
*/
csc = (format->code == MEDIA_BUS_FMT_AYUV8_1X32) !=
(sink_format->code == MEDIA_BUS_FMT_AYUV8_1X32);
if (csc && (flags & V4L2_MBUS_FRAMEFMT_SET_CSC)) {
format->ycbcr_enc = fmt->format.ycbcr_enc;
format->quantization = fmt->format.quantization;
} else {
format->ycbcr_enc = sink_format->ycbcr_enc;
format->quantization = sink_format->quantization;
}
vsp1_entity_adjust_color_space(format);
fmt->format = *format;
fmt->format.flags = flags;
return 0;
}
format->code = fmt->format.code;
format->width = clamp_t(unsigned int, fmt->format.width,
RWPF_MIN_WIDTH, rwpf->entity.max_width);
format->height = clamp_t(unsigned int, fmt->format.height,
RWPF_MIN_HEIGHT, rwpf->entity.max_height);
format->field = V4L2_FIELD_NONE;
format->colorspace = fmt->format.colorspace;
format->xfer_func = fmt->format.xfer_func;
format->ycbcr_enc = fmt->format.ycbcr_enc;
format->quantization = fmt->format.quantization;
vsp1_entity_adjust_color_space(format);
fmt->format = *format;
if (rwpf->entity.type == VSP1_ENTITY_RPF) {
struct v4l2_rect *crop;
/* Update the sink crop rectangle. */
crop = v4l2_subdev_state_get_crop(state, RWPF_PAD_SINK);
crop->left = 0;
crop->top = 0;
crop->width = fmt->format.width;
crop->height = fmt->format.height;
}
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SOURCE);
*format = fmt->format;
if (rwpf->flip.rotate) {
format->width = fmt->format.height;
format->height = fmt->format.width;
}
return 0;
}
static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_rwpf *rwpf = to_rwpf(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
/*
* Cropping is only supported on the RPF and is implemented on the sink
* pad.
*/
if (rwpf->entity.type == VSP1_ENTITY_WPF || sel->pad != RWPF_PAD_SINK)
return -EINVAL;
guard(mutex)(&rwpf->entity.lock);
state = vsp1_entity_get_state(&rwpf->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP:
sel->r = *v4l2_subdev_state_get_crop(state, RWPF_PAD_SINK);
break;
case V4L2_SEL_TGT_CROP_BOUNDS:
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = format->width;
sel->r.height = format->height;
break;
default:
return -EINVAL;
}
return 0;
}
static int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
unsigned int min_width = RWPF_MIN_WIDTH;
unsigned int min_height = RWPF_MIN_HEIGHT;
struct vsp1_rwpf *rwpf = to_rwpf(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *crop;
/*
* Cropping is only supported on the RPF and is implemented on the sink
* pad.
*/
if (rwpf->entity.type == VSP1_ENTITY_WPF || sel->pad != RWPF_PAD_SINK)
return -EINVAL;
if (sel->target != V4L2_SEL_TGT_CROP)
return -EINVAL;
guard(mutex)(&rwpf->entity.lock);
state = vsp1_entity_get_state(&rwpf->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
/* Make sure the crop rectangle is entirely contained in the image. */
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
/*
* For YUV formats, restrict the crop rectangle coordinates to multiples
* of 2 to avoid shifting the color plane.
*/
if (format->code == MEDIA_BUS_FMT_AYUV8_1X32) {
sel->r.left = ALIGN(sel->r.left, 2);
sel->r.top = ALIGN(sel->r.top, 2);
sel->r.width = round_down(sel->r.width, 2);
sel->r.height = round_down(sel->r.height, 2);
/*
* The RPF doesn't enforces the alignment constraint on the sink
* pad format, which could have an odd size, possibly down to
* 1x1. In that case, the minimum width and height would be
* smaller than the sink pad format, leading to a negative upper
* bound in the left and top clamping. Clamp the minimum width
* and height to the format width and height to avoid this.
*
* In such a situation, odd values for the crop rectangle size
* would be accepted when clamping the width and height below.
* While that would create an invalid hardware configuration,
* the video device enforces proper alignment of the pixel
* format, and the mismatch will then result in link validation
* failure. Incorrect operation of the hardware is not possible.
*/
min_width = min(ALIGN(min_width, 2), format->width);
min_height = min(ALIGN(min_height, 2), format->height);
}
sel->r.left = clamp_t(int, sel->r.left, 0, format->width - min_width);
sel->r.top = clamp_t(int, sel->r.top, 0, format->height - min_height);
sel->r.width = clamp(sel->r.width, min_width,
format->width - sel->r.left);
sel->r.height = clamp(sel->r.height, min_height,
format->height - sel->r.top);
crop = v4l2_subdev_state_get_crop(state, RWPF_PAD_SINK);
*crop = sel->r;
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, RWPF_PAD_SOURCE);
format->width = crop->width;
format->height = crop->height;
return 0;
}
static const struct v4l2_subdev_pad_ops vsp1_rwpf_pad_ops = {
.enum_mbus_code = vsp1_rwpf_enum_mbus_code,
.enum_frame_size = vsp1_rwpf_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_rwpf_set_format,
.get_selection = vsp1_rwpf_get_selection,
.set_selection = vsp1_rwpf_set_selection,
};
const struct v4l2_subdev_ops vsp1_rwpf_subdev_ops = {
.core = &vsp1_entity_core_ops,
.pad = &vsp1_rwpf_pad_ops,
};
/* -----------------------------------------------------------------------------
* Controls
*/
static int vsp1_rwpf_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_rwpf *rwpf =
container_of(ctrl->handler, struct vsp1_rwpf, ctrls);
switch (ctrl->id) {
case V4L2_CID_ALPHA_COMPONENT:
rwpf->alpha = ctrl->val;
break;
}
return 0;
}
static const struct v4l2_ctrl_ops vsp1_rwpf_ctrl_ops = {
.s_ctrl = vsp1_rwpf_s_ctrl,
};
int vsp1_rwpf_init_ctrls(struct vsp1_rwpf *rwpf, unsigned int ncontrols)
{
rwpf->entity.codes = rwpf_codes;
rwpf->entity.num_codes = ARRAY_SIZE(rwpf_codes);
v4l2_ctrl_handler_init(&rwpf->ctrls, ncontrols + 1);
v4l2_ctrl_new_std(&rwpf->ctrls, &vsp1_rwpf_ctrl_ops,
V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255);
rwpf->entity.subdev.ctrl_handler = &rwpf->ctrls;
return rwpf->ctrls.error;
}
@@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_RWPF_H__
#define __VSP1_RWPF_H__
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_entity.h"
#define RWPF_PAD_SINK 0
#define RWPF_PAD_SOURCE 1
#define RWPF_MIN_WIDTH 1
#define RWPF_MIN_HEIGHT 1
struct v4l2_ctrl;
struct vsp1_dl_manager;
struct vsp1_rwpf;
struct vsp1_video;
struct vsp1_rwpf_memory {
dma_addr_t addr[3];
};
struct vsp1_rwpf {
struct vsp1_entity entity;
struct v4l2_ctrl_handler ctrls;
struct vsp1_video *video;
struct v4l2_pix_format_mplane format;
const struct vsp1_format_info *fmtinfo;
unsigned int brx_input;
unsigned int alpha;
u32 mult_alpha;
u32 outfmt;
struct {
spinlock_t lock;
struct {
struct v4l2_ctrl *vflip;
struct v4l2_ctrl *hflip;
struct v4l2_ctrl *rotate;
} ctrls;
unsigned int pending;
unsigned int active;
bool rotate;
} flip;
struct vsp1_rwpf_memory mem;
bool writeback;
struct vsp1_dl_manager *dlm;
};
static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_rwpf, entity.subdev);
}
static inline struct vsp1_rwpf *entity_to_rwpf(struct vsp1_entity *entity)
{
return container_of(entity, struct vsp1_rwpf, entity);
}
struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index);
struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index);
void vsp1_wpf_stop(struct vsp1_rwpf *wpf);
int vsp1_rwpf_init_ctrls(struct vsp1_rwpf *rwpf, unsigned int ncontrols);
extern const struct v4l2_subdev_ops vsp1_rwpf_subdev_ops;
#endif /* __VSP1_RWPF_H__ */
@@ -0,0 +1,385 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_sru.c -- R-Car VSP1 Super Resolution Unit
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/mutex.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_pipe.h"
#include "vsp1_sru.h"
#define SRU_MIN_SIZE 4U
#define SRU_MAX_SIZE 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_sru_write(struct vsp1_sru *sru,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg, data);
}
/* -----------------------------------------------------------------------------
* Controls
*/
#define V4L2_CID_VSP1_SRU_INTENSITY (V4L2_CID_USER_BASE | 0x1001)
struct vsp1_sru_param {
u32 ctrl0;
u32 ctrl2;
};
#define VI6_SRU_CTRL0_PARAMS(p0, p1) \
(((p0) << VI6_SRU_CTRL0_PARAM0_SHIFT) | \
((p1) << VI6_SRU_CTRL0_PARAM1_SHIFT))
#define VI6_SRU_CTRL2_PARAMS(p6, p7, p8) \
(((p6) << VI6_SRU_CTRL2_PARAM6_SHIFT) | \
((p7) << VI6_SRU_CTRL2_PARAM7_SHIFT) | \
((p8) << VI6_SRU_CTRL2_PARAM8_SHIFT))
static const struct vsp1_sru_param vsp1_sru_params[] = {
{
.ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(24, 40, 255),
}, {
.ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(8, 16, 255),
}, {
.ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(36, 60, 255),
}, {
.ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(12, 27, 255),
}, {
.ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(48, 80, 255),
}, {
.ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN,
.ctrl2 = VI6_SRU_CTRL2_PARAMS(16, 36, 255),
},
};
static int sru_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_sru *sru =
container_of(ctrl->handler, struct vsp1_sru, ctrls);
switch (ctrl->id) {
case V4L2_CID_VSP1_SRU_INTENSITY:
sru->intensity = ctrl->val;
break;
}
return 0;
}
static const struct v4l2_ctrl_ops sru_ctrl_ops = {
.s_ctrl = sru_s_ctrl,
};
static const struct v4l2_ctrl_config sru_intensity_control = {
.ops = &sru_ctrl_ops,
.id = V4L2_CID_VSP1_SRU_INTENSITY,
.name = "Intensity",
.type = V4L2_CTRL_TYPE_INTEGER,
.min = 1,
.max = 6,
.def = 1,
.step = 1,
};
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const unsigned int sru_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static int sru_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct vsp1_sru *sru = to_sru(subdev);
int ret;
ret = vsp1_subdev_enum_frame_size(subdev, sd_state, fse);
if (ret)
return ret;
if (fse->pad == SRU_PAD_SOURCE) {
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
state = vsp1_entity_get_state(&sru->entity, sd_state,
fse->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, SRU_PAD_SINK);
guard(mutex)(&sru->entity.lock);
fse->min_width = format->width;
fse->min_height = format->height;
if (format->width <= SRU_MAX_SIZE / 2 &&
format->height <= SRU_MAX_SIZE / 2) {
fse->max_width = format->width * 2;
fse->max_height = format->height * 2;
} else {
fse->max_width = format->width;
fse->max_height = format->height;
}
}
return 0;
}
static void sru_try_format(struct vsp1_sru *sru,
struct v4l2_subdev_state *sd_state,
unsigned int pad, struct v4l2_mbus_framefmt *fmt)
{
struct v4l2_mbus_framefmt *format;
unsigned int input_area;
unsigned int output_area;
switch (pad) {
case SRU_PAD_SINK:
/* Default to YUV if the requested format is not supported. */
if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
fmt->code != MEDIA_BUS_FMT_AYUV8_1X32)
fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
vsp1_entity_adjust_color_space(fmt);
fmt->width = clamp(fmt->width, SRU_MIN_SIZE, SRU_MAX_SIZE);
fmt->height = clamp(fmt->height, SRU_MIN_SIZE, SRU_MAX_SIZE);
break;
case SRU_PAD_SOURCE:
/* The SRU can't perform format conversion. */
format = v4l2_subdev_state_get_format(sd_state, SRU_PAD_SINK);
fmt->code = format->code;
fmt->colorspace = format->colorspace;
fmt->xfer_func = format->xfer_func;
fmt->ycbcr_enc = format->ycbcr_enc;
fmt->quantization = format->quantization;
/*
* We can upscale by 2 in both direction, but not independently.
* Compare the input and output rectangles areas (avoiding
* integer overflows on the output): if the requested output
* area is larger than 1.5^2 the input area upscale by two,
* otherwise don't scale.
*/
input_area = format->width * format->height;
output_area = min(fmt->width, SRU_MAX_SIZE)
* min(fmt->height, SRU_MAX_SIZE);
if (fmt->width <= SRU_MAX_SIZE / 2 &&
fmt->height <= SRU_MAX_SIZE / 2 &&
output_area > input_area * 9 / 4) {
fmt->width = format->width * 2;
fmt->height = format->height * 2;
} else {
fmt->width = format->width;
fmt->height = format->height;
}
break;
}
fmt->field = V4L2_FIELD_NONE;
}
static int sru_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_sru *sru = to_sru(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
guard(mutex)(&sru->entity.lock);
state = vsp1_entity_get_state(&sru->entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
sru_try_format(sru, state, fmt->pad, &fmt->format);
format = v4l2_subdev_state_get_format(state, fmt->pad);
*format = fmt->format;
if (fmt->pad == SRU_PAD_SINK) {
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, SRU_PAD_SOURCE);
*format = fmt->format;
sru_try_format(sru, state, SRU_PAD_SOURCE, format);
}
return 0;
}
static const struct v4l2_subdev_pad_ops sru_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = sru_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = sru_set_format,
};
static const struct v4l2_subdev_ops sru_ops = {
.core = &vsp1_entity_core_ops,
.pad = &sru_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void sru_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
const struct vsp1_sru_param *param;
struct vsp1_sru *sru = to_sru(&entity->subdev);
struct v4l2_mbus_framefmt *input;
struct v4l2_mbus_framefmt *output;
u32 ctrl0;
input = v4l2_subdev_state_get_format(state, SRU_PAD_SINK);
output = v4l2_subdev_state_get_format(state, SRU_PAD_SOURCE);
if (input->code == MEDIA_BUS_FMT_ARGB8888_1X32)
ctrl0 = VI6_SRU_CTRL0_PARAM2 | VI6_SRU_CTRL0_PARAM3
| VI6_SRU_CTRL0_PARAM4;
else
ctrl0 = VI6_SRU_CTRL0_PARAM3;
if (input->width != output->width)
ctrl0 |= VI6_SRU_CTRL0_MODE_UPSCALE;
param = &vsp1_sru_params[sru->intensity - 1];
ctrl0 |= param->ctrl0;
vsp1_sru_write(sru, dlb, VI6_SRU_CTRL0, ctrl0);
vsp1_sru_write(sru, dlb, VI6_SRU_CTRL1, VI6_SRU_CTRL1_PARAM5);
vsp1_sru_write(sru, dlb, VI6_SRU_CTRL2, param->ctrl2);
}
static unsigned int sru_max_width(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe)
{
struct v4l2_mbus_framefmt *input;
struct v4l2_mbus_framefmt *output;
input = v4l2_subdev_state_get_format(state, SRU_PAD_SINK);
output = v4l2_subdev_state_get_format(state, SRU_PAD_SOURCE);
/*
* The maximum input width of the SRU is 288 input pixels, but 32
* pixels are reserved to support overlapping partition windows when
* scaling.
*/
if (input->width != output->width)
return 512;
else
return 256;
}
static void sru_partition(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int partition_idx,
struct v4l2_rect *window)
{
struct v4l2_mbus_framefmt *input;
struct v4l2_mbus_framefmt *output;
input = v4l2_subdev_state_get_format(state, SRU_PAD_SINK);
output = v4l2_subdev_state_get_format(state, SRU_PAD_SOURCE);
/* Adapt if SRUx2 is enabled. */
if (input->width != output->width) {
window->width /= 2;
window->left /= 2;
window->height /= 2;
window->top /= 2;
}
partition->sru = *window;
}
static const struct vsp1_entity_operations sru_entity_ops = {
.configure_stream = sru_configure_stream,
.max_width = sru_max_width,
.partition = sru_partition,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1)
{
struct vsp1_sru *sru;
int ret;
sru = devm_kzalloc(vsp1->dev, sizeof(*sru), GFP_KERNEL);
if (sru == NULL)
return ERR_PTR(-ENOMEM);
sru->entity.ops = &sru_entity_ops;
sru->entity.type = VSP1_ENTITY_SRU;
sru->entity.codes = sru_codes;
sru->entity.num_codes = ARRAY_SIZE(sru_codes);
sru->entity.min_width = SRU_MIN_SIZE;
sru->entity.max_width = SRU_MAX_SIZE;
sru->entity.min_height = SRU_MIN_SIZE;
sru->entity.max_height = SRU_MAX_SIZE;
ret = vsp1_entity_init(vsp1, &sru->entity, "sru", 2, &sru_ops,
MEDIA_ENT_F_PROC_VIDEO_SCALER);
if (ret < 0)
return ERR_PTR(ret);
/* Initialize the control handler. */
v4l2_ctrl_handler_init(&sru->ctrls, 1);
v4l2_ctrl_new_custom(&sru->ctrls, &sru_intensity_control, NULL);
sru->intensity = 1;
sru->entity.subdev.ctrl_handler = &sru->ctrls;
if (sru->ctrls.error) {
dev_err(vsp1->dev, "sru: failed to initialize controls\n");
ret = sru->ctrls.error;
vsp1_entity_destroy(&sru->entity);
return ERR_PTR(ret);
}
return sru;
}
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_sru.h -- R-Car VSP1 Super Resolution Unit
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_SRU_H__
#define __VSP1_SRU_H__
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define SRU_PAD_SINK 0
#define SRU_PAD_SOURCE 1
struct vsp1_sru {
struct vsp1_entity entity;
struct v4l2_ctrl_handler ctrls;
unsigned int intensity;
};
static inline struct vsp1_sru *to_sru(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_sru, entity.subdev);
}
struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1);
#endif /* __VSP1_SRU_H__ */
@@ -0,0 +1,410 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_uds.c -- R-Car VSP1 Up and Down Scaler
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/mutex.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_pipe.h"
#include "vsp1_uds.h"
#define UDS_MIN_SIZE 4U
#define UDS_MAX_SIZE 8190U
#define UDS_MIN_FACTOR 0x0100
#define UDS_MAX_FACTOR 0xffff
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_uds_write(struct vsp1_uds *uds,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg + uds->entity.index * VI6_UDS_OFFSET, data);
}
/* -----------------------------------------------------------------------------
* Scaling Computation
*/
void vsp1_uds_set_alpha(struct vsp1_entity *entity, struct vsp1_dl_body *dlb,
unsigned int alpha)
{
struct vsp1_uds *uds = to_uds(&entity->subdev);
vsp1_uds_write(uds, dlb, VI6_UDS_ALPVAL,
alpha << VI6_UDS_ALPVAL_VAL0_SHIFT);
}
/*
* uds_output_size - Return the output size for an input size and scaling ratio
* @input: input size in pixels
* @ratio: scaling ratio in U4.12 fixed-point format
*/
static unsigned int uds_output_size(unsigned int input, unsigned int ratio)
{
if (ratio > 4096) {
/* Down-scaling */
unsigned int mp;
mp = ratio / 4096;
mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4);
return (input - 1) / mp * mp * 4096 / ratio + 1;
} else {
/* Up-scaling */
return (input - 1) * 4096 / ratio + 1;
}
}
/*
* uds_output_limits - Return the min and max output sizes for an input size
* @input: input size in pixels
* @minimum: minimum output size (returned)
* @maximum: maximum output size (returned)
*/
static void uds_output_limits(unsigned int input,
unsigned int *minimum, unsigned int *maximum)
{
*minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE);
*maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE);
}
/*
* uds_passband_width - Return the passband filter width for a scaling ratio
* @ratio: scaling ratio in U4.12 fixed-point format
*/
static unsigned int uds_passband_width(unsigned int ratio)
{
if (ratio >= 4096) {
/* Down-scaling */
unsigned int mp;
mp = ratio / 4096;
mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4);
return 64 * 4096 * mp / ratio;
} else {
/* Up-scaling */
return 64;
}
}
static unsigned int uds_compute_ratio(unsigned int input, unsigned int output)
{
/* TODO: This is an approximation that will need to be refined. */
return (input - 1) * 4096 / (output - 1);
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Pad Operations
*/
static const unsigned int uds_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static int uds_enum_frame_size(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct vsp1_uds *uds = to_uds(subdev);
int ret;
ret = vsp1_subdev_enum_frame_size(subdev, sd_state, fse);
if (ret)
return ret;
if (fse->pad == UDS_PAD_SOURCE) {
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
state = vsp1_entity_get_state(&uds->entity, sd_state,
fse->which);
if (!state)
return -EINVAL;
format = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
guard(mutex)(&uds->entity.lock);
uds_output_limits(format->width, &fse->min_width,
&fse->max_width);
uds_output_limits(format->height, &fse->min_height,
&fse->max_height);
}
return 0;
}
static void uds_try_format(struct vsp1_uds *uds,
struct v4l2_subdev_state *sd_state,
unsigned int pad, struct v4l2_mbus_framefmt *fmt)
{
struct v4l2_mbus_framefmt *format;
unsigned int minimum;
unsigned int maximum;
switch (pad) {
case UDS_PAD_SINK:
/* Default to YUV if the requested format is not supported. */
if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 &&
fmt->code != MEDIA_BUS_FMT_AYUV8_1X32)
fmt->code = MEDIA_BUS_FMT_AYUV8_1X32;
vsp1_entity_adjust_color_space(fmt);
fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE);
fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE);
break;
case UDS_PAD_SOURCE:
/* The UDS scales but can't perform format conversion. */
format = v4l2_subdev_state_get_format(sd_state, UDS_PAD_SINK);
fmt->code = format->code;
fmt->colorspace = format->colorspace;
fmt->xfer_func = format->xfer_func;
fmt->ycbcr_enc = format->ycbcr_enc;
fmt->quantization = format->quantization;
uds_output_limits(format->width, &minimum, &maximum);
fmt->width = clamp(fmt->width, minimum, maximum);
uds_output_limits(format->height, &minimum, &maximum);
fmt->height = clamp(fmt->height, minimum, maximum);
break;
}
fmt->field = V4L2_FIELD_NONE;
}
static int uds_set_format(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct vsp1_uds *uds = to_uds(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
guard(mutex)(&uds->entity.lock);
state = vsp1_entity_get_state(&uds->entity, sd_state, fmt->which);
if (!state)
return -EINVAL;
uds_try_format(uds, state, fmt->pad, &fmt->format);
format = v4l2_subdev_state_get_format(state, fmt->pad);
*format = fmt->format;
if (fmt->pad == UDS_PAD_SINK) {
/* Propagate the format to the source pad. */
format = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
*format = fmt->format;
uds_try_format(uds, state, UDS_PAD_SOURCE, format);
}
return 0;
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const struct v4l2_subdev_pad_ops uds_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = uds_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = uds_set_format,
};
static const struct v4l2_subdev_ops uds_ops = {
.pad = &uds_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void uds_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_uds *uds = to_uds(&entity->subdev);
const struct v4l2_mbus_framefmt *output;
const struct v4l2_mbus_framefmt *input;
unsigned int hscale;
unsigned int vscale;
bool multitap;
input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
hscale = uds_compute_ratio(input->width, output->width);
vscale = uds_compute_ratio(input->height, output->height);
dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale);
/*
* Multi-tap scaling can't be enabled along with alpha scaling when
* scaling down with a factor lower than or equal to 1/2 in either
* direction.
*/
if (uds->scale_alpha && (hscale >= 8192 || vscale >= 8192))
multitap = false;
else
multitap = true;
vsp1_uds_write(uds, dlb, VI6_UDS_CTRL,
(uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) |
(multitap ? VI6_UDS_CTRL_BC : 0));
vsp1_uds_write(uds, dlb, VI6_UDS_PASS_BWIDTH,
(uds_passband_width(hscale)
<< VI6_UDS_PASS_BWIDTH_H_SHIFT) |
(uds_passband_width(vscale)
<< VI6_UDS_PASS_BWIDTH_V_SHIFT));
/* Set the scaling ratios. */
vsp1_uds_write(uds, dlb, VI6_UDS_SCALE,
(hscale << VI6_UDS_SCALE_HFRAC_SHIFT) |
(vscale << VI6_UDS_SCALE_VFRAC_SHIFT));
}
static void uds_configure_partition(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_uds *uds = to_uds(&entity->subdev);
/* Input size clipping. */
vsp1_uds_write(uds, dlb, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN |
(0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) |
(partition->uds_sink.width
<< VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT));
/* Output size clipping. */
vsp1_uds_write(uds, dlb, VI6_UDS_CLIP_SIZE,
(partition->uds_source.width
<< VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) |
(partition->uds_source.height
<< VI6_UDS_CLIP_SIZE_VSIZE_SHIFT));
}
static unsigned int uds_max_width(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe)
{
const struct v4l2_mbus_framefmt *output;
const struct v4l2_mbus_framefmt *input;
unsigned int hscale;
input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
hscale = output->width / input->width;
/*
* The maximum width of the UDS is 304 pixels. These are input pixels
* in the event of up-scaling, and output pixels in the event of
* downscaling.
*
* To support overlapping partition windows we clamp at units of 256 and
* the remaining pixels are reserved.
*/
if (hscale <= 2)
return 256;
else if (hscale <= 4)
return 512;
else if (hscale <= 8)
return 1024;
else
return 2048;
}
/* -----------------------------------------------------------------------------
* Partition Algorithm Support
*/
static void uds_partition(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int partition_idx,
struct v4l2_rect *window)
{
const struct v4l2_mbus_framefmt *output;
const struct v4l2_mbus_framefmt *input;
input = v4l2_subdev_state_get_format(state, UDS_PAD_SINK);
output = v4l2_subdev_state_get_format(state, UDS_PAD_SOURCE);
partition->uds_sink.width = window->width * input->width
/ output->width;
partition->uds_sink.left = window->left * input->width
/ output->width;
partition->uds_sink.height = input->height;
partition->uds_sink.top = 0;
partition->uds_source = *window;
*window = partition->uds_sink;
}
static const struct vsp1_entity_operations uds_entity_ops = {
.configure_stream = uds_configure_stream,
.configure_partition = uds_configure_partition,
.max_width = uds_max_width,
.partition = uds_partition,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index)
{
struct vsp1_uds *uds;
char name[6];
int ret;
uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL);
if (uds == NULL)
return ERR_PTR(-ENOMEM);
uds->entity.ops = &uds_entity_ops;
uds->entity.type = VSP1_ENTITY_UDS;
uds->entity.index = index;
uds->entity.codes = uds_codes;
uds->entity.num_codes = ARRAY_SIZE(uds_codes);
uds->entity.min_width = UDS_MIN_SIZE;
uds->entity.max_width = UDS_MAX_SIZE;
uds->entity.min_height = UDS_MIN_SIZE;
uds->entity.max_height = UDS_MAX_SIZE;
sprintf(name, "uds.%u", index);
ret = vsp1_entity_init(vsp1, &uds->entity, name, 2, &uds_ops,
MEDIA_ENT_F_PROC_VIDEO_SCALER);
if (ret < 0)
return ERR_PTR(ret);
return uds;
}
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_uds.h -- R-Car VSP1 Up and Down Scaler
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_UDS_H__
#define __VSP1_UDS_H__
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include "vsp1_entity.h"
struct vsp1_device;
#define UDS_PAD_SINK 0
#define UDS_PAD_SOURCE 1
struct vsp1_uds {
struct vsp1_entity entity;
bool scale_alpha;
};
static inline struct vsp1_uds *to_uds(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_uds, entity.subdev);
}
struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index);
void vsp1_uds_set_alpha(struct vsp1_entity *uds, struct vsp1_dl_body *dlb,
unsigned int alpha);
#endif /* __VSP1_UDS_H__ */
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_uif.c -- R-Car VSP1 User Logic Interface
*
* Copyright (C) 2017-2018 Laurent Pinchart
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <linux/gfp.h>
#include <linux/sys_soc.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_entity.h"
#include "vsp1_uif.h"
#define UIF_MIN_SIZE 4U
#define UIF_MAX_SIZE 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline u32 vsp1_uif_read(struct vsp1_uif *uif, u32 reg)
{
return vsp1_read(uif->entity.vsp1,
uif->entity.index * VI6_UIF_OFFSET + reg);
}
static inline void vsp1_uif_write(struct vsp1_uif *uif,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg + uif->entity.index * VI6_UIF_OFFSET, data);
}
u32 vsp1_uif_get_crc(struct vsp1_uif *uif)
{
return vsp1_uif_read(uif, VI6_UIF_DISCOM_DOCMCCRCR);
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Pad Operations
*/
static const unsigned int uif_codes[] = {
MEDIA_BUS_FMT_ARGB8888_1X32,
MEDIA_BUS_FMT_AHSV8888_1X32,
MEDIA_BUS_FMT_AYUV8_1X32,
};
static int uif_get_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_uif *uif = to_uif(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
if (sel->pad != UIF_PAD_SINK)
return -EINVAL;
guard(mutex)(&uif->entity.lock);
state = vsp1_entity_get_state(&uif->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
switch (sel->target) {
case V4L2_SEL_TGT_CROP_BOUNDS:
case V4L2_SEL_TGT_CROP_DEFAULT:
format = v4l2_subdev_state_get_format(state, UIF_PAD_SINK);
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = format->width;
sel->r.height = format->height;
break;
case V4L2_SEL_TGT_CROP:
sel->r = *v4l2_subdev_state_get_crop(state, sel->pad);
break;
default:
return -EINVAL;
}
return 0;
}
static int uif_set_selection(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *sel)
{
struct vsp1_uif *uif = to_uif(subdev);
struct v4l2_subdev_state *state;
struct v4l2_mbus_framefmt *format;
struct v4l2_rect *selection;
if (sel->pad != UIF_PAD_SINK ||
sel->target != V4L2_SEL_TGT_CROP)
return -EINVAL;
guard(mutex)(&uif->entity.lock);
state = vsp1_entity_get_state(&uif->entity, sd_state, sel->which);
if (!state)
return -EINVAL;
/* The crop rectangle must be inside the input frame. */
format = v4l2_subdev_state_get_format(state, UIF_PAD_SINK);
sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1);
sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1);
sel->r.width = clamp_t(unsigned int, sel->r.width, UIF_MIN_SIZE,
format->width - sel->r.left);
sel->r.height = clamp_t(unsigned int, sel->r.height, UIF_MIN_SIZE,
format->height - sel->r.top);
/* Store the crop rectangle. */
selection = v4l2_subdev_state_get_crop(state, sel->pad);
*selection = sel->r;
return 0;
}
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
static const struct v4l2_subdev_pad_ops uif_pad_ops = {
.enum_mbus_code = vsp1_subdev_enum_mbus_code,
.enum_frame_size = vsp1_subdev_enum_frame_size,
.get_fmt = vsp1_subdev_get_pad_format,
.set_fmt = vsp1_subdev_set_pad_format,
.get_selection = uif_get_selection,
.set_selection = uif_set_selection,
};
static const struct v4l2_subdev_ops uif_ops = {
.pad = &uif_pad_ops,
};
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
static void uif_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_uif *uif = to_uif(&entity->subdev);
const struct v4l2_rect *crop;
unsigned int left;
unsigned int width;
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMPMR,
VI6_UIF_DISCOM_DOCMPMR_SEL(9));
crop = v4l2_subdev_state_get_crop(state, UIF_PAD_SINK);
left = crop->left;
width = crop->width;
/* On M3-W the horizontal coordinates are twice the register value. */
if (uif->m3w_quirk) {
left /= 2;
width /= 2;
}
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPXR, left);
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPYR, crop->top);
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZXR, width);
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZYR, crop->height);
vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMCR,
VI6_UIF_DISCOM_DOCMCR_CMPR);
}
static const struct vsp1_entity_operations uif_entity_ops = {
.configure_stream = uif_configure_stream,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
static const struct soc_device_attribute vsp1_r8a7796[] = {
{ .soc_id = "r8a7796" },
{ /* sentinel */ }
};
struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index)
{
struct vsp1_uif *uif;
char name[6];
int ret;
uif = devm_kzalloc(vsp1->dev, sizeof(*uif), GFP_KERNEL);
if (!uif)
return ERR_PTR(-ENOMEM);
if (soc_device_match(vsp1_r8a7796))
uif->m3w_quirk = true;
uif->entity.ops = &uif_entity_ops;
uif->entity.type = VSP1_ENTITY_UIF;
uif->entity.index = index;
uif->entity.codes = uif_codes;
uif->entity.num_codes = ARRAY_SIZE(uif_codes);
uif->entity.min_width = UIF_MIN_SIZE;
uif->entity.min_height = UIF_MIN_SIZE;
uif->entity.max_width = UIF_MAX_SIZE;
uif->entity.max_height = UIF_MAX_SIZE;
/* The datasheet names the two UIF instances UIF4 and UIF5. */
sprintf(name, "uif.%u", index + 4);
ret = vsp1_entity_init(vsp1, &uif->entity, name, 2, &uif_ops,
MEDIA_ENT_F_PROC_VIDEO_STATISTICS);
if (ret < 0)
return ERR_PTR(ret);
return uif;
}
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_uif.h -- R-Car VSP1 User Logic Interface
*
* Copyright (C) 2017-2018 Laurent Pinchart
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_UIF_H__
#define __VSP1_UIF_H__
#include "vsp1_entity.h"
struct vsp1_device;
#define UIF_PAD_SINK 0
#define UIF_PAD_SOURCE 1
struct vsp1_uif {
struct vsp1_entity entity;
bool m3w_quirk;
};
static inline struct vsp1_uif *to_uif(struct v4l2_subdev *subdev)
{
return container_of(subdev, struct vsp1_uif, entity.subdev);
}
struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index);
u32 vsp1_uif_get_crc(struct vsp1_uif *uif);
#endif /* __VSP1_UIF_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_video.h -- R-Car VSP1 Video Node
*
* Copyright (C) 2013-2015 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#ifndef __VSP1_VIDEO_H__
#define __VSP1_VIDEO_H__
#include <linux/list.h>
#include <linux/spinlock.h>
#include <media/videobuf2-v4l2.h>
#include "vsp1_rwpf.h"
struct vsp1_vb2_buffer {
struct vb2_v4l2_buffer buf;
struct list_head queue;
struct vsp1_rwpf_memory mem;
};
static inline struct vsp1_vb2_buffer *
to_vsp1_vb2_buffer(struct vb2_v4l2_buffer *vbuf)
{
return container_of(vbuf, struct vsp1_vb2_buffer, buf);
}
struct vsp1_video {
struct list_head list;
struct vsp1_device *vsp1;
struct vsp1_rwpf *rwpf;
struct video_device video;
enum v4l2_buf_type type;
struct media_pad pad;
struct mutex lock;
unsigned int pipe_index;
struct vb2_queue queue;
spinlock_t irqlock;
struct list_head irqqueue;
};
static inline struct vsp1_video *to_vsp1_video(struct video_device *vdev)
{
return container_of(vdev, struct vsp1_video, video);
}
void vsp1_video_suspend(struct vsp1_device *vsp1);
void vsp1_video_resume(struct vsp1_device *vsp1);
struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1,
struct vsp1_rwpf *rwpf);
void vsp1_video_cleanup(struct vsp1_video *video);
#endif /* __VSP1_VIDEO_H__ */
@@ -0,0 +1,634 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_vspx.c -- R-Car Gen 4 VSPX
*
* Copyright (C) 2025 Ideas On Board Oy
* Copyright (C) 2025 Renesas Electronics Corporation
*/
#include "vsp1_vspx.h"
#include <linux/cleanup.h>
#include <linux/container_of.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <media/media-entity.h>
#include <media/v4l2-subdev.h>
#include <media/vsp1.h>
#include "vsp1_dl.h"
#include "vsp1_iif.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
/*
* struct vsp1_vspx_pipeline - VSPX pipeline
* @pipe: the VSP1 pipeline
* @partition: the pre-calculated partition used by the pipeline
* @mutex: protects the streaming start/stop sequences
* @lock: protect access to the enabled flag
* @enabled: the enable flag
* @vspx_frame_end: frame end callback
* @frame_end_data: data for the frame end callback
*/
struct vsp1_vspx_pipeline {
struct vsp1_pipeline pipe;
struct vsp1_partition partition;
/*
* Protects the streaming start/stop sequences.
*
* The start/stop sequences cannot be locked with the 'lock' spinlock
* as they acquire mutexes when handling the pm runtime and the vsp1
* pipe start/stop operations. Provide a dedicated mutex for this
* reason.
*/
struct mutex mutex;
/*
* Protects the enable flag.
*
* The enabled flag is contended between the start/stop streaming
* routines and the job_run one, which cannot take a mutex as it is
* called from the ISP irq context.
*/
spinlock_t lock;
bool enabled;
void (*vspx_frame_end)(void *frame_end_data);
void *frame_end_data;
};
static inline struct vsp1_vspx_pipeline *
to_vsp1_vspx_pipeline(struct vsp1_pipeline *pipe)
{
return container_of(pipe, struct vsp1_vspx_pipeline, pipe);
}
/*
* struct vsp1_vspx - VSPX device
* @vsp1: the VSP1 device
* @pipe: the VSPX pipeline
*/
struct vsp1_vspx {
struct vsp1_device *vsp1;
struct vsp1_vspx_pipeline pipe;
};
/* Apply the given width, height and fourcc to the RWPF's subdevice */
static int vsp1_vspx_rwpf_set_subdev_fmt(struct vsp1_device *vsp1,
struct vsp1_rwpf *rwpf,
u32 isp_fourcc,
unsigned int width,
unsigned int height)
{
struct vsp1_entity *ent = &rwpf->entity;
struct v4l2_subdev_format format = {};
u32 vspx_fourcc;
switch (isp_fourcc) {
case V4L2_PIX_FMT_GREY:
/* 8 bit RAW Bayer image. */
vspx_fourcc = V4L2_PIX_FMT_RGB332;
break;
case V4L2_PIX_FMT_Y10:
case V4L2_PIX_FMT_Y12:
case V4L2_PIX_FMT_Y16:
/* 10, 12 and 16 bit RAW Bayer image. */
vspx_fourcc = V4L2_PIX_FMT_RGB565;
break;
case V4L2_META_FMT_GENERIC_8:
/* ConfigDMA parameters buffer. */
vspx_fourcc = V4L2_PIX_FMT_XBGR32;
break;
default:
return -EINVAL;
}
rwpf->fmtinfo = vsp1_get_format_info(vsp1, vspx_fourcc);
format.which = V4L2_SUBDEV_FORMAT_ACTIVE;
format.pad = RWPF_PAD_SINK;
format.format.width = width;
format.format.height = height;
format.format.field = V4L2_FIELD_NONE;
format.format.code = rwpf->fmtinfo->mbus;
return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, &format);
}
/* Configure the RPF->IIF->WPF pipeline for ConfigDMA or RAW image transfer. */
static int vsp1_vspx_pipeline_configure(struct vsp1_device *vsp1,
dma_addr_t addr, u32 isp_fourcc,
unsigned int width, unsigned int height,
unsigned int stride,
unsigned int iif_sink_pad,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
struct vsp1_rwpf *rpf0 = pipe->inputs[0];
int ret;
ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, rpf0, isp_fourcc, width,
height);
if (ret)
return ret;
ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, pipe->output, isp_fourcc,
width, height);
if (ret)
return ret;
vsp1_pipeline_calculate_partition(pipe, &pipe->part_table[0], width, 0);
rpf0->format.plane_fmt[0].bytesperline = stride;
rpf0->format.num_planes = 1;
rpf0->mem.addr[0] = addr;
/*
* Connect RPF0 to the IIF sink pad corresponding to the config or image
* path.
*/
rpf0->entity.sink_pad = iif_sink_pad;
vsp1_entity_route_setup(&rpf0->entity, pipe, dlb);
vsp1_entity_configure_stream(&rpf0->entity, rpf0->entity.state, pipe,
dl, dlb);
vsp1_entity_configure_partition(&rpf0->entity, pipe,
&pipe->part_table[0], dl, dlb);
return 0;
}
/* -----------------------------------------------------------------------------
* Interrupt handling
*/
static void vsp1_vspx_pipeline_frame_end(struct vsp1_pipeline *pipe,
unsigned int completion)
{
struct vsp1_vspx_pipeline *vspx_pipe = to_vsp1_vspx_pipeline(pipe);
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
/*
* Operating the vsp1_pipe in singleshot mode requires to
* manually set the pipeline state to stopped when a transfer
* is completed.
*/
pipe->state = VSP1_PIPELINE_STOPPED;
}
if (vspx_pipe->vspx_frame_end)
vspx_pipe->vspx_frame_end(vspx_pipe->frame_end_data);
}
/* -----------------------------------------------------------------------------
* ISP Driver API (include/media/vsp1.h)
*/
/**
* vsp1_isp_init() - Initialize the VSPX
* @dev: The VSP1 struct device
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_init(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
if (!vsp1)
return -EPROBE_DEFER;
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_init);
/**
* vsp1_isp_get_bus_master - Get VSPX bus master
* @dev: The VSP1 struct device
*
* The VSPX accesses memory through an FCPX instance. When allocating memory
* buffers that will have to be accessed by the VSPX the 'struct device' of
* the FCPX should be used. Use this function to get a reference to it.
*
* Return: a pointer to the bus master's device
*/
struct device *vsp1_isp_get_bus_master(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
if (!vsp1)
return ERR_PTR(-ENODEV);
return vsp1->bus_master;
}
EXPORT_SYMBOL_GPL(vsp1_isp_get_bus_master);
/**
* vsp1_isp_alloc_buffer - Allocate a buffer in the VSPX address space
* @dev: The VSP1 struct device
* @size: The size of the buffer to be allocated by the VSPX
* @buffer_desc: The buffer descriptor. Will be filled with the buffer
* CPU-mapped address, the bus address and the size of the
* allocated buffer
*
* Allocate a buffer that will be later accessed by the VSPX. Buffers allocated
* using vsp1_isp_alloc_buffer() shall be released with a call to
* vsp1_isp_free_buffer(). This function is used by the ISP driver to allocate
* memory for the ConfigDMA parameters buffer.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_alloc_buffer(struct device *dev, size_t size,
struct vsp1_isp_buffer_desc *buffer_desc)
{
struct device *bus_master = vsp1_isp_get_bus_master(dev);
if (IS_ERR_OR_NULL(bus_master))
return -ENODEV;
buffer_desc->cpu_addr = dma_alloc_coherent(bus_master, size,
&buffer_desc->dma_addr,
GFP_KERNEL);
if (!buffer_desc->cpu_addr)
return -ENOMEM;
buffer_desc->size = size;
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_alloc_buffer);
/**
* vsp1_isp_free_buffer - Release a buffer allocated by vsp1_isp_alloc_buffer()
* @dev: The VSP1 struct device
* @buffer_desc: The descriptor of the buffer to release as returned by
* vsp1_isp_alloc_buffer()
*
* Release memory in the VSPX address space allocated by
* vsp1_isp_alloc_buffer().
*/
void vsp1_isp_free_buffer(struct device *dev,
struct vsp1_isp_buffer_desc *buffer_desc)
{
struct device *bus_master = vsp1_isp_get_bus_master(dev);
if (IS_ERR_OR_NULL(bus_master))
return;
dma_free_coherent(bus_master, buffer_desc->size, buffer_desc->cpu_addr,
buffer_desc->dma_addr);
}
EXPORT_SYMBOL_GPL(vsp1_isp_free_buffer);
/**
* vsp1_isp_start_streaming - Start processing VSPX jobs
* @dev: The VSP1 struct device
* @frame_end: The frame end callback description
*
* Start the VSPX and prepare for accepting buffer transfer job requests.
* The caller is responsible for tracking the started state of the VSPX.
* Attempting to start an already started VSPX instance is an error.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_start_streaming(struct device *dev,
struct vsp1_vspx_frame_end *frame_end)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
u32 value;
int ret;
if (!frame_end)
return -EINVAL;
guard(mutex)(&vspx_pipe->mutex);
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
if (vspx_pipe->enabled)
return -EBUSY;
}
vspx_pipe->vspx_frame_end = frame_end->vspx_frame_end;
vspx_pipe->frame_end_data = frame_end->frame_end_data;
/* Enable the VSP1 and prepare for streaming. */
vsp1_pipeline_dump(pipe, "VSPX job");
ret = vsp1_device_get(vsp1);
if (ret < 0)
return ret;
/*
* Make sure VSPX is not active. This should never happen in normal
* usage
*/
value = vsp1_read(vsp1, VI6_CMD(0));
if (value & VI6_CMD_STRCMD) {
dev_err(vsp1->dev,
"%s: Starting of WPF0 already reserved\n", __func__);
ret = -EBUSY;
goto error_put;
}
value = vsp1_read(vsp1, VI6_STATUS);
if (value & VI6_STATUS_SYS_ACT(0)) {
dev_err(vsp1->dev,
"%s: WPF0 has not entered idle state\n", __func__);
ret = -EBUSY;
goto error_put;
}
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
vspx_pipe->enabled = true;
}
return 0;
error_put:
vsp1_device_put(vsp1);
return ret;
}
EXPORT_SYMBOL_GPL(vsp1_isp_start_streaming);
/**
* vsp1_isp_stop_streaming - Stop the VSPX
* @dev: The VSP1 struct device
*
* Stop the VSPX operation by stopping the vsp1 pipeline and waiting for the
* last frame in transfer, if any, to complete.
*
* The caller is responsible for tracking the stopped state of the VSPX.
* Attempting to stop an already stopped VSPX instance is a nop.
*/
void vsp1_isp_stop_streaming(struct device *dev)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
guard(mutex)(&vspx_pipe->mutex);
scoped_guard(spinlock_irq, &vspx_pipe->lock) {
if (!vspx_pipe->enabled)
return;
vspx_pipe->enabled = false;
}
WARN_ON_ONCE(vsp1_pipeline_stop(pipe));
vspx_pipe->vspx_frame_end = NULL;
vsp1_dlm_reset(pipe->output->dlm);
vsp1_device_put(vsp1);
}
EXPORT_SYMBOL_GPL(vsp1_isp_stop_streaming);
/**
* vsp1_isp_job_prepare - Prepare a new buffer transfer job
* @dev: The VSP1 struct device
* @job: The job description
*
* Prepare a new buffer transfer job by populating a display list that will be
* later executed by a call to vsp1_isp_job_run(). All pending jobs must be
* released after stopping the streaming operations with a call to
* vsp1_isp_job_release().
*
* In order for the VSPX to accept new jobs to prepare the VSPX must have been
* started.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_job_prepare(struct device *dev, struct vsp1_isp_job_desc *job)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
const struct v4l2_pix_format_mplane *pix_mp;
struct vsp1_dl_list *second_dl = NULL;
struct vsp1_dl_body *dlb;
struct vsp1_dl_list *dl;
int ret;
/*
* Transfer the buffers described in the job: an optional ConfigDMA
* parameters buffer and a RAW image.
*/
job->dl = vsp1_dl_list_get(pipe->output->dlm);
if (!job->dl)
return -ENOMEM;
dl = job->dl;
dlb = vsp1_dl_list_get_body0(dl);
/* Configure IIF routing and enable IIF function. */
vsp1_entity_route_setup(pipe->iif, pipe, dlb);
vsp1_entity_configure_stream(pipe->iif, pipe->iif->state, pipe,
dl, dlb);
/* Configure WPF0 to enable RPF0 as source. */
vsp1_entity_route_setup(&pipe->output->entity, pipe, dlb);
vsp1_entity_configure_stream(&pipe->output->entity,
pipe->output->entity.state, pipe,
dl, dlb);
if (job->config.pairs) {
/*
* Writing less than 17 pairs corrupts the output images ( < 16
* pairs) or freezes the VSPX operations (= 16 pairs). Only
* allow more than 16 pairs to be written.
*/
if (job->config.pairs <= 16) {
ret = -EINVAL;
goto error_put_dl;
}
/*
* Configure RPF0 for ConfigDMA data. Transfer the number of
* configuration pairs plus 2 words for the header.
*/
ret = vsp1_vspx_pipeline_configure(vsp1, job->config.mem,
V4L2_META_FMT_GENERIC_8,
job->config.pairs * 2 + 2, 1,
job->config.pairs * 2 + 2,
VSPX_IIF_SINK_PAD_CONFIG,
dl, dlb);
if (ret)
goto error_put_dl;
second_dl = vsp1_dl_list_get(pipe->output->dlm);
if (!second_dl) {
ret = -ENOMEM;
goto error_put_dl;
}
dl = second_dl;
dlb = vsp1_dl_list_get_body0(dl);
}
/* Configure RPF0 for RAW image transfer. */
pix_mp = &job->img.fmt;
ret = vsp1_vspx_pipeline_configure(vsp1, job->img.mem,
pix_mp->pixelformat,
pix_mp->width, pix_mp->height,
pix_mp->plane_fmt[0].bytesperline,
VSPX_IIF_SINK_PAD_IMG, dl, dlb);
if (ret)
goto error_put_dl;
if (second_dl)
vsp1_dl_list_add_chain(job->dl, second_dl);
return 0;
error_put_dl:
if (second_dl)
vsp1_dl_list_put(second_dl);
vsp1_dl_list_put(job->dl);
job->dl = NULL;
return ret;
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_prepare);
/**
* vsp1_isp_job_run - Run a buffer transfer job
* @dev: The VSP1 struct device
* @job: The job to be run
*
* Run the display list contained in the job description provided by the caller.
* The job must have been prepared with a call to vsp1_isp_job_prepare() and
* the job's display list shall be valid.
*
* Jobs can be run only on VSPX instances which have been started. Requests
* to run a job after the VSPX has been stopped return -EINVAL and the job
* resources shall be released by the caller with vsp1_isp_job_release().
* When a job is run successfully all the resources acquired by
* vsp1_isp_job_prepare() are released by this function and no further action
* is required to the caller.
*
* Return: %0 on success or a negative error code on failure
*/
int vsp1_isp_job_run(struct device *dev, struct vsp1_isp_job_desc *job)
{
struct vsp1_device *vsp1 = dev_get_drvdata(dev);
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
struct vsp1_pipeline *pipe = &vspx_pipe->pipe;
u32 value;
/* Make sure VSPX is not busy processing a frame. */
value = vsp1_read(vsp1, VI6_CMD(0));
if (value) {
dev_err(vsp1->dev,
"%s: Starting of WPF0 already reserved\n", __func__);
return -EBUSY;
}
scoped_guard(spinlock_irqsave, &vspx_pipe->lock) {
/*
* If a new job is scheduled when the VSPX is stopped, do not
* run it.
*/
if (!vspx_pipe->enabled)
return -EINVAL;
vsp1_dl_list_commit(job->dl, 0);
/*
* The display list is now under control of the display list
* manager and will be released automatically when the job
* completes.
*/
job->dl = NULL;
}
scoped_guard(spinlock_irqsave, &pipe->irqlock) {
vsp1_pipeline_run(pipe);
}
return 0;
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_run);
/**
* vsp1_isp_job_release - Release a non processed transfer job
* @dev: The VSP1 struct device
* @job: The job to release
*
* Release a job prepared by a call to vsp1_isp_job_prepare() and not yet
* run. All pending jobs shall be released after streaming has been stopped.
*/
void vsp1_isp_job_release(struct device *dev,
struct vsp1_isp_job_desc *job)
{
vsp1_dl_list_put(job->dl);
}
EXPORT_SYMBOL_GPL(vsp1_isp_job_release);
/* -----------------------------------------------------------------------------
* Initialization and cleanup
*/
int vsp1_vspx_init(struct vsp1_device *vsp1)
{
struct vsp1_vspx_pipeline *vspx_pipe;
struct vsp1_pipeline *pipe;
vsp1->vspx = devm_kzalloc(vsp1->dev, sizeof(*vsp1->vspx), GFP_KERNEL);
if (!vsp1->vspx)
return -ENOMEM;
vsp1->vspx->vsp1 = vsp1;
vspx_pipe = &vsp1->vspx->pipe;
vspx_pipe->enabled = false;
pipe = &vspx_pipe->pipe;
vsp1_pipeline_init(pipe);
pipe->partitions = 1;
pipe->part_table = &vspx_pipe->partition;
pipe->interlaced = false;
pipe->frame_end = vsp1_vspx_pipeline_frame_end;
mutex_init(&vspx_pipe->mutex);
spin_lock_init(&vspx_pipe->lock);
/*
* Initialize RPF0 as input for VSPX and use it unconditionally for
* now.
*/
pipe->inputs[0] = vsp1->rpf[0];
pipe->inputs[0]->entity.pipe = pipe;
pipe->inputs[0]->entity.sink = &vsp1->iif->entity;
list_add_tail(&pipe->inputs[0]->entity.list_pipe, &pipe->entities);
pipe->iif = &vsp1->iif->entity;
pipe->iif->pipe = pipe;
pipe->iif->sink = &vsp1->wpf[0]->entity;
pipe->iif->sink_pad = RWPF_PAD_SINK;
list_add_tail(&pipe->iif->list_pipe, &pipe->entities);
pipe->output = vsp1->wpf[0];
pipe->output->entity.pipe = pipe;
list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities);
return 0;
}
void vsp1_vspx_cleanup(struct vsp1_device *vsp1)
{
struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe;
mutex_destroy(&vspx_pipe->mutex);
}
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* vsp1_vspx.h -- R-Car Gen 4 VSPX
*
* Copyright (C) 2025 Ideas On Board Oy
* Copyright (C) 2025 Renesas Electronics Corporation
*/
#ifndef __VSP1_VSPX_H__
#define __VSP1_VSPX_H__
#include "vsp1.h"
int vsp1_vspx_init(struct vsp1_device *vsp1);
void vsp1_vspx_cleanup(struct vsp1_device *vsp1);
#endif /* __VSP1_VSPX_H__ */
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*/
#include <linux/device.h>
#include <media/v4l2-subdev.h>
#include "vsp1.h"
#include "vsp1_dl.h"
#include "vsp1_pipe.h"
#include "vsp1_rwpf.h"
#include "vsp1_video.h"
#define WPF_GEN2_MAX_WIDTH 2048U
#define WPF_GEN2_MAX_HEIGHT 2048U
#define WPF_GEN3_MAX_WIDTH 8190U
#define WPF_GEN3_MAX_HEIGHT 8190U
/* -----------------------------------------------------------------------------
* Device Access
*/
static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf,
struct vsp1_dl_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, reg + wpf->entity.index * VI6_WPF_OFFSET, data);
}
/* -----------------------------------------------------------------------------
* Controls
*/
enum wpf_flip_ctrl {
WPF_CTRL_VFLIP = 0,
WPF_CTRL_HFLIP = 1,
};
static int vsp1_wpf_set_rotation(struct vsp1_rwpf *wpf, unsigned int rotation)
{
struct vsp1_video *video = wpf->video;
struct v4l2_mbus_framefmt *sink_format;
struct v4l2_mbus_framefmt *source_format;
bool rotate;
/*
* Only consider the 0°/180° from/to 90°/270° modifications, the rest
* is taken care of by the flipping configuration.
*/
rotate = rotation == 90 || rotation == 270;
if (rotate == wpf->flip.rotate)
return 0;
/* Changing rotation isn't allowed when buffers are allocated. */
guard(mutex)(&video->lock);
if (vb2_is_busy(&video->queue))
return -EBUSY;
sink_format = v4l2_subdev_state_get_format(wpf->entity.state,
RWPF_PAD_SINK);
source_format = v4l2_subdev_state_get_format(wpf->entity.state,
RWPF_PAD_SOURCE);
guard(mutex)(&wpf->entity.lock);
if (rotate) {
source_format->width = sink_format->height;
source_format->height = sink_format->width;
} else {
source_format->width = sink_format->width;
source_format->height = sink_format->height;
}
wpf->flip.rotate = rotate;
return 0;
}
static int vsp1_wpf_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct vsp1_rwpf *wpf =
container_of(ctrl->handler, struct vsp1_rwpf, ctrls);
unsigned int rotation;
u32 flip = 0;
int ret;
/* Update the rotation. */
rotation = wpf->flip.ctrls.rotate ? wpf->flip.ctrls.rotate->val : 0;
ret = vsp1_wpf_set_rotation(wpf, rotation);
if (ret < 0)
return ret;
/*
* Compute the flip value resulting from all three controls, with
* rotation by 180° flipping the image in both directions. Store the
* result in the pending flip field for the next frame that will be
* processed.
*/
if (wpf->flip.ctrls.vflip->val)
flip |= BIT(WPF_CTRL_VFLIP);
if (wpf->flip.ctrls.hflip && wpf->flip.ctrls.hflip->val)
flip |= BIT(WPF_CTRL_HFLIP);
if (rotation == 180 || rotation == 270)
flip ^= BIT(WPF_CTRL_VFLIP) | BIT(WPF_CTRL_HFLIP);
guard(spinlock_irq)(&wpf->flip.lock);
wpf->flip.pending = flip;
return 0;
}
static const struct v4l2_ctrl_ops vsp1_wpf_ctrl_ops = {
.s_ctrl = vsp1_wpf_s_ctrl,
};
static int wpf_init_controls(struct vsp1_rwpf *wpf)
{
struct vsp1_device *vsp1 = wpf->entity.vsp1;
unsigned int num_flip_ctrls;
int ret;
spin_lock_init(&wpf->flip.lock);
if (wpf->entity.index != 0) {
/* Only WPF0 supports flipping. */
num_flip_ctrls = 0;
} else if (vsp1_feature(vsp1, VSP1_HAS_WPF_HFLIP)) {
/*
* When horizontal flip is supported the WPF implements three
* controls (horizontal flip, vertical flip and rotation).
*/
num_flip_ctrls = 3;
} else if (vsp1_feature(vsp1, VSP1_HAS_WPF_VFLIP)) {
/*
* When only vertical flip is supported the WPF implements a
* single control (vertical flip).
*/
num_flip_ctrls = 1;
} else {
/* Otherwise flipping is not supported. */
num_flip_ctrls = 0;
}
ret = vsp1_rwpf_init_ctrls(wpf, num_flip_ctrls);
if (ret < 0)
return ret;
if (num_flip_ctrls >= 1) {
wpf->flip.ctrls.vflip =
v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops,
V4L2_CID_VFLIP, 0, 1, 1, 0);
}
if (num_flip_ctrls == 3) {
wpf->flip.ctrls.hflip =
v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops,
V4L2_CID_HFLIP, 0, 1, 1, 0);
wpf->flip.ctrls.rotate =
v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops,
V4L2_CID_ROTATE, 0, 270, 90, 0);
v4l2_ctrl_cluster(3, &wpf->flip.ctrls.vflip);
}
if (wpf->ctrls.error)
return wpf->ctrls.error;
return 0;
}
/* -----------------------------------------------------------------------------
* VSP1 Entity Operations
*/
void vsp1_wpf_stop(struct vsp1_rwpf *wpf)
{
struct vsp1_device *vsp1 = wpf->entity.vsp1;
/*
* Write to registers directly when stopping the stream as there will be
* no pipeline run to apply the display list.
*/
vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), 0);
vsp1_write(vsp1, wpf->entity.index * VI6_WPF_OFFSET +
VI6_WPF_SRCRPF, 0);
}
static void vsp1_wpf_destroy(struct vsp1_entity *entity)
{
struct vsp1_rwpf *wpf = entity_to_rwpf(entity);
vsp1_dlm_destroy(wpf->dlm);
}
static int wpf_configure_writeback_chain(struct vsp1_rwpf *wpf,
struct vsp1_dl_list *dl)
{
unsigned int index = wpf->entity.index;
struct vsp1_dl_list *dl_next;
struct vsp1_dl_body *dlb;
dl_next = vsp1_dl_list_get(wpf->dlm);
if (!dl_next) {
dev_err(wpf->entity.vsp1->dev,
"Failed to obtain a dl list, disabling writeback\n");
return -ENOMEM;
}
dlb = vsp1_dl_list_get_body0(dl_next);
vsp1_dl_body_write(dlb, VI6_WPF_WRBCK_CTRL(index), 0);
vsp1_dl_list_add_chain(dl, dl_next);
return 0;
}
static void wpf_configure_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev);
struct vsp1_device *vsp1 = wpf->entity.vsp1;
const struct v4l2_mbus_framefmt *source_format;
const struct v4l2_mbus_framefmt *sink_format;
unsigned int index = wpf->entity.index;
unsigned int i;
u32 outfmt = 0;
u32 srcrpf = 0;
int ret;
sink_format = v4l2_subdev_state_get_format(state, RWPF_PAD_SINK);
source_format = v4l2_subdev_state_get_format(state, RWPF_PAD_SOURCE);
/*
* Format configuration. Skip for IIF (VSPX) or if the pipe doesn't
* write to memory.
*/
if (!pipe->iif && (!pipe->lif || wpf->writeback)) {
const struct v4l2_pix_format_mplane *format = &wpf->format;
const struct vsp1_format_info *fmtinfo = wpf->fmtinfo;
outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT;
if (wpf->flip.rotate)
outfmt |= VI6_WPF_OUTFMT_ROT;
if (fmtinfo->alpha)
outfmt |= VI6_WPF_OUTFMT_PXA;
if (fmtinfo->swap_yc)
outfmt |= VI6_WPF_OUTFMT_SPYCS;
if (fmtinfo->swap_uv)
outfmt |= VI6_WPF_OUTFMT_SPUVS;
/* Destination stride and byte swapping. */
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_Y,
format->plane_fmt[0].bytesperline);
if (format->num_planes > 1)
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_C,
format->plane_fmt[1].bytesperline);
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSWAP, fmtinfo->swap);
if (vsp1_feature(vsp1, VSP1_HAS_WPF_HFLIP) && index == 0)
vsp1_wpf_write(wpf, dlb, VI6_WPF_ROT_CTRL,
VI6_WPF_ROT_CTRL_LN16 |
(256 << VI6_WPF_ROT_CTRL_LMEM_WD_SHIFT));
}
if (sink_format->code != source_format->code) {
u16 ycbcr_enc;
u16 quantization;
u32 wrtm;
if (sink_format->code == MEDIA_BUS_FMT_AYUV8_1X32) {
ycbcr_enc = sink_format->ycbcr_enc;
quantization = sink_format->quantization;
} else {
ycbcr_enc = source_format->ycbcr_enc;
quantization = source_format->quantization;
}
if (ycbcr_enc == V4L2_YCBCR_ENC_601 &&
quantization == V4L2_QUANTIZATION_LIM_RANGE)
wrtm = VI6_WPF_OUTFMT_WRTM_BT601;
else if (ycbcr_enc == V4L2_YCBCR_ENC_601 &&
quantization == V4L2_QUANTIZATION_FULL_RANGE)
wrtm = VI6_WPF_OUTFMT_WRTM_BT601_EXT;
else if (ycbcr_enc == V4L2_YCBCR_ENC_709 &&
quantization == V4L2_QUANTIZATION_LIM_RANGE)
wrtm = VI6_WPF_OUTFMT_WRTM_BT709;
else
wrtm = VI6_WPF_OUTFMT_WRTM_BT709_EXT;
outfmt |= VI6_WPF_OUTFMT_CSC | wrtm;
}
wpf->outfmt = outfmt;
vsp1_dl_body_write(dlb, VI6_DPR_WPF_FPORCH(index),
VI6_DPR_WPF_FPORCH_FP_WPFN);
/*
* Sources. If the pipeline has a single input and BRx is not used,
* configure it as the master layer. Otherwise configure all
* inputs as sub-layers and select the virtual RPF as the master
* layer. For VSPX configure the enabled sources as masters.
*/
for (i = 0; i < vsp1->info->rpf_count; ++i) {
struct vsp1_rwpf *input = pipe->inputs[i];
if (!input)
continue;
srcrpf |= (pipe->iif || (!pipe->brx && pipe->num_inputs == 1))
? VI6_WPF_SRCRPF_RPF_ACT_MST(input->entity.index)
: VI6_WPF_SRCRPF_RPF_ACT_SUB(input->entity.index);
}
if (pipe->brx)
srcrpf |= pipe->brx->type == VSP1_ENTITY_BRU
? VI6_WPF_SRCRPF_VIRACT_MST
: VI6_WPF_SRCRPF_VIRACT2_MST;
vsp1_wpf_write(wpf, dlb, VI6_WPF_SRCRPF, srcrpf);
/* Enable interrupts. */
vsp1_dl_body_write(dlb, VI6_WPF_IRQ_STA(index), 0);
vsp1_dl_body_write(dlb, VI6_WPF_IRQ_ENB(index),
VI6_WPF_IRQ_ENB_DFEE);
if (pipe->iif)
return;
/*
* Configure writeback for display pipelines (the wpf writeback flag is
* never set for memory-to-memory pipelines). Start by adding a chained
* display list to disable writeback after a single frame, and process
* to enable writeback. If the display list allocation fails don't
* enable writeback as we wouldn't be able to safely disable it,
* resulting in possible memory corruption.
*/
if (wpf->writeback) {
ret = wpf_configure_writeback_chain(wpf, dl);
if (ret < 0)
wpf->writeback = false;
}
vsp1_dl_body_write(dlb, VI6_WPF_WRBCK_CTRL(index),
wpf->writeback ? VI6_WPF_WRBCK_CTRL_WBMD : 0);
}
static void wpf_configure_frame(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
const unsigned int mask = BIT(WPF_CTRL_VFLIP)
| BIT(WPF_CTRL_HFLIP);
struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev);
u32 outfmt;
scoped_guard(spinlock_irqsave, &wpf->flip.lock) {
wpf->flip.active = (wpf->flip.active & ~mask)
| (wpf->flip.pending & mask);
}
outfmt = (wpf->alpha << VI6_WPF_OUTFMT_PDV_SHIFT) | wpf->outfmt;
if (wpf->flip.active & BIT(WPF_CTRL_VFLIP))
outfmt |= VI6_WPF_OUTFMT_FLP;
if (wpf->flip.active & BIT(WPF_CTRL_HFLIP))
outfmt |= VI6_WPF_OUTFMT_HFLP;
vsp1_wpf_write(wpf, dlb, VI6_WPF_OUTFMT, outfmt);
}
static void wpf_configure_partition(struct vsp1_entity *entity,
struct vsp1_pipeline *pipe,
const struct vsp1_partition *partition,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev);
struct vsp1_device *vsp1 = wpf->entity.vsp1;
struct vsp1_rwpf_memory mem = wpf->mem;
const struct v4l2_pix_format_mplane *format = &wpf->format;
const struct vsp1_format_info *fmtinfo = wpf->fmtinfo;
unsigned int width;
unsigned int height;
unsigned int left;
unsigned int offset;
unsigned int flip;
unsigned int i;
/*
* Cropping. The partition algorithm can split the image into multiple
* slices.
*/
width = partition->wpf.width;
left = partition->wpf.left;
height = partition->wpf.height;
vsp1_wpf_write(wpf, dlb, VI6_WPF_HSZCLIP, VI6_WPF_SZCLIP_EN |
(0 << VI6_WPF_SZCLIP_OFST_SHIFT) |
(width << VI6_WPF_SZCLIP_SIZE_SHIFT));
vsp1_wpf_write(wpf, dlb, VI6_WPF_VSZCLIP, VI6_WPF_SZCLIP_EN |
(0 << VI6_WPF_SZCLIP_OFST_SHIFT) |
(height << VI6_WPF_SZCLIP_SIZE_SHIFT));
/*
* For display pipelines without writeback enabled there's no memory
* address to configure, return now.
*/
if (pipe->lif && !wpf->writeback)
return;
/*
* Update the memory offsets based on flipping configuration.
* The destination addresses point to the locations where the
* VSP starts writing to memory, which can be any corner of the
* image depending on the combination of flipping and rotation.
*/
/*
* First take the partition left coordinate into account.
* Compute the offset to order the partitions correctly on the
* output based on whether flipping is enabled. Consider
* horizontal flipping when rotation is disabled but vertical
* flipping when rotation is enabled, as rotating the image
* switches the horizontal and vertical directions. The offset
* is applied horizontally or vertically accordingly.
*/
flip = wpf->flip.active;
if (flip & BIT(WPF_CTRL_HFLIP) && !wpf->flip.rotate)
offset = format->width - left - width;
else if (flip & BIT(WPF_CTRL_VFLIP) && wpf->flip.rotate)
offset = format->height - left - width;
else
offset = left;
for (i = 0; i < format->num_planes; ++i) {
unsigned int hsub = i > 0 ? fmtinfo->hsub : 1;
unsigned int vsub = i > 0 ? fmtinfo->vsub : 1;
if (wpf->flip.rotate)
mem.addr[i] += offset / vsub
* format->plane_fmt[i].bytesperline;
else
mem.addr[i] += offset / hsub
* fmtinfo->bpp[i] / 8;
}
if (flip & BIT(WPF_CTRL_VFLIP)) {
/*
* When rotating the output (after rotation) image
* height is equal to the partition width (before
* rotation). Otherwise it is equal to the output
* image height.
*/
if (wpf->flip.rotate)
height = width;
else
height = format->height;
mem.addr[0] += (height - 1)
* format->plane_fmt[0].bytesperline;
if (format->num_planes > 1) {
offset = (height / fmtinfo->vsub - 1)
* format->plane_fmt[1].bytesperline;
mem.addr[1] += offset;
mem.addr[2] += offset;
}
}
if (wpf->flip.rotate && !(flip & BIT(WPF_CTRL_HFLIP))) {
unsigned int hoffset = max(0, (int)format->width - 16);
/*
* Compute the output coordinate. The partition
* horizontal (left) offset becomes a vertical offset.
*/
for (i = 0; i < format->num_planes; ++i) {
unsigned int hsub = i > 0 ? fmtinfo->hsub : 1;
mem.addr[i] += hoffset / hsub
* fmtinfo->bpp[i] / 8;
}
}
/*
* On Gen3+ hardware the SPUVS bit has no effect on 3-planar
* formats. Swap the U and V planes manually in that case.
*/
if (vsp1->info->gen >= 3 && format->num_planes == 3 &&
fmtinfo->swap_uv)
swap(mem.addr[1], mem.addr[2]);
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_Y, mem.addr[0]);
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C0, mem.addr[1]);
vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C1, mem.addr[2]);
/*
* Writeback operates in single-shot mode and lasts for a single frame,
* reset the writeback flag to false for the next frame.
*/
wpf->writeback = false;
}
static unsigned int wpf_max_width(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe)
{
struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev);
return wpf->flip.rotate ? 256 : wpf->entity.max_width;
}
static void wpf_partition(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_partition *partition,
unsigned int partition_idx,
struct v4l2_rect *window)
{
partition->wpf = *window;
}
static const struct vsp1_entity_operations wpf_entity_ops = {
.destroy = vsp1_wpf_destroy,
.configure_stream = wpf_configure_stream,
.configure_frame = wpf_configure_frame,
.configure_partition = wpf_configure_partition,
.max_width = wpf_max_width,
.partition = wpf_partition,
};
/* -----------------------------------------------------------------------------
* Initialization and Cleanup
*/
struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index)
{
struct vsp1_rwpf *wpf;
char name[6];
int ret;
wpf = devm_kzalloc(vsp1->dev, sizeof(*wpf), GFP_KERNEL);
if (wpf == NULL)
return ERR_PTR(-ENOMEM);
wpf->entity.min_width = RWPF_MIN_WIDTH;
wpf->entity.min_height = RWPF_MIN_HEIGHT;
if (vsp1->info->gen == 2) {
wpf->entity.max_width = WPF_GEN2_MAX_WIDTH;
wpf->entity.max_height = WPF_GEN2_MAX_HEIGHT;
} else {
wpf->entity.max_width = WPF_GEN3_MAX_WIDTH;
wpf->entity.max_height = WPF_GEN3_MAX_HEIGHT;
}
wpf->entity.ops = &wpf_entity_ops;
wpf->entity.type = VSP1_ENTITY_WPF;
wpf->entity.index = index;
sprintf(name, "wpf.%u", index);
ret = vsp1_entity_init(vsp1, &wpf->entity, name, 2, &vsp1_rwpf_subdev_ops,
MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER);
if (ret < 0)
return ERR_PTR(ret);
/* Initialize the display list manager. */
wpf->dlm = vsp1_dlm_create(vsp1, index, 64);
if (!wpf->dlm) {
ret = -ENOMEM;
goto error;
}
/* Initialize the control handler. */
ret = wpf_init_controls(wpf);
if (ret < 0) {
dev_err(vsp1->dev, "wpf%u: failed to initialize controls\n",
index);
goto error;
}
v4l2_ctrl_handler_setup(&wpf->ctrls);
return wpf;
error:
vsp1_entity_destroy(&wpf->entity);
return ERR_PTR(ret);
}