mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
12 KiB
453 lines
12 KiB
// 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 ([email protected]) |
|
*/ |
|
|
|
#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_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 |
|
*/ |
|
|
|
/* |
|
* 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_mbus_code(struct v4l2_subdev *subdev, |
|
struct v4l2_subdev_pad_config *cfg, |
|
struct v4l2_subdev_mbus_code_enum *code) |
|
{ |
|
static const unsigned int codes[] = { |
|
MEDIA_BUS_FMT_ARGB8888_1X32, |
|
MEDIA_BUS_FMT_AYUV8_1X32, |
|
}; |
|
|
|
return vsp1_subdev_enum_mbus_code(subdev, cfg, code, codes, |
|
ARRAY_SIZE(codes)); |
|
} |
|
|
|
static int brx_enum_frame_size(struct v4l2_subdev *subdev, |
|
struct v4l2_subdev_pad_config *cfg, |
|
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 struct v4l2_rect *brx_get_compose(struct vsp1_brx *brx, |
|
struct v4l2_subdev_pad_config *cfg, |
|
unsigned int pad) |
|
{ |
|
return v4l2_subdev_get_try_compose(&brx->entity.subdev, cfg, pad); |
|
} |
|
|
|
static void brx_try_format(struct vsp1_brx *brx, |
|
struct v4l2_subdev_pad_config *config, |
|
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; |
|
break; |
|
|
|
default: |
|
/* The BRx can't perform format conversion. */ |
|
format = vsp1_entity_get_pad_format(&brx->entity, config, |
|
BRX_PAD_SINK(0)); |
|
fmt->code = format->code; |
|
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; |
|
fmt->colorspace = V4L2_COLORSPACE_SRGB; |
|
} |
|
|
|
static int brx_set_format(struct v4l2_subdev *subdev, |
|
struct v4l2_subdev_pad_config *cfg, |
|
struct v4l2_subdev_format *fmt) |
|
{ |
|
struct vsp1_brx *brx = to_brx(subdev); |
|
struct v4l2_subdev_pad_config *config; |
|
struct v4l2_mbus_framefmt *format; |
|
int ret = 0; |
|
|
|
mutex_lock(&brx->entity.lock); |
|
|
|
config = vsp1_entity_get_pad_config(&brx->entity, cfg, fmt->which); |
|
if (!config) { |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
|
|
brx_try_format(brx, config, fmt->pad, &fmt->format); |
|
|
|
format = vsp1_entity_get_pad_format(&brx->entity, config, fmt->pad); |
|
*format = fmt->format; |
|
|
|
/* Reset the compose rectangle. */ |
|
if (fmt->pad != brx->entity.source_pad) { |
|
struct v4l2_rect *compose; |
|
|
|
compose = brx_get_compose(brx, config, 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 = vsp1_entity_get_pad_format(&brx->entity, |
|
config, i); |
|
format->code = fmt->format.code; |
|
} |
|
} |
|
|
|
done: |
|
mutex_unlock(&brx->entity.lock); |
|
return ret; |
|
} |
|
|
|
static int brx_get_selection(struct v4l2_subdev *subdev, |
|
struct v4l2_subdev_pad_config *cfg, |
|
struct v4l2_subdev_selection *sel) |
|
{ |
|
struct vsp1_brx *brx = to_brx(subdev); |
|
struct v4l2_subdev_pad_config *config; |
|
|
|
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: |
|
config = vsp1_entity_get_pad_config(&brx->entity, cfg, |
|
sel->which); |
|
if (!config) |
|
return -EINVAL; |
|
|
|
mutex_lock(&brx->entity.lock); |
|
sel->r = *brx_get_compose(brx, config, sel->pad); |
|
mutex_unlock(&brx->entity.lock); |
|
return 0; |
|
|
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
static int brx_set_selection(struct v4l2_subdev *subdev, |
|
struct v4l2_subdev_pad_config *cfg, |
|
struct v4l2_subdev_selection *sel) |
|
{ |
|
struct vsp1_brx *brx = to_brx(subdev); |
|
struct v4l2_subdev_pad_config *config; |
|
struct v4l2_mbus_framefmt *format; |
|
struct v4l2_rect *compose; |
|
int ret = 0; |
|
|
|
if (sel->pad == brx->entity.source_pad) |
|
return -EINVAL; |
|
|
|
if (sel->target != V4L2_SEL_TGT_COMPOSE) |
|
return -EINVAL; |
|
|
|
mutex_lock(&brx->entity.lock); |
|
|
|
config = vsp1_entity_get_pad_config(&brx->entity, cfg, sel->which); |
|
if (!config) { |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
|
|
/* |
|
* The compose rectangle top left corner must be inside the output |
|
* frame. |
|
*/ |
|
format = vsp1_entity_get_pad_format(&brx->entity, config, |
|
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 = vsp1_entity_get_pad_format(&brx->entity, config, sel->pad); |
|
sel->r.width = format->width; |
|
sel->r.height = format->height; |
|
|
|
compose = brx_get_compose(brx, config, sel->pad); |
|
*compose = sel->r; |
|
|
|
done: |
|
mutex_unlock(&brx->entity.lock); |
|
return ret; |
|
} |
|
|
|
static const struct v4l2_subdev_pad_ops brx_pad_ops = { |
|
.init_cfg = vsp1_entity_init_cfg, |
|
.enum_mbus_code = brx_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 = { |
|
.pad = &brx_pad_ops, |
|
}; |
|
|
|
/* ----------------------------------------------------------------------------- |
|
* VSP1 Entity Operations |
|
*/ |
|
|
|
static void brx_configure_stream(struct vsp1_entity *entity, |
|
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 = vsp1_entity_get_pad_format(&brx->entity, brx->entity.config, |
|
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; |
|
|
|
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; |
|
}
|
|
|