497 lines
13 KiB
C
497 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022 MediaTek Inc.
|
|
* Author: Ping-Hsun Wu <ping-hsun.wu@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/math64.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <media/videobuf2-v4l2.h>
|
|
#include <media/videobuf2-dma-contig.h>
|
|
#include "mtk-mdp3-core.h"
|
|
#include "mtk-mdp3-regs.h"
|
|
#include "mtk-mdp3-m2m.h"
|
|
|
|
static const struct mdp_format *mdp_find_fmt(const struct mtk_mdp_driver_data *mdp_data,
|
|
u32 pixelformat, u32 type)
|
|
{
|
|
u32 i, flag;
|
|
|
|
flag = V4L2_TYPE_IS_OUTPUT(type) ? MDP_FMT_FLAG_OUTPUT :
|
|
MDP_FMT_FLAG_CAPTURE;
|
|
for (i = 0; i < mdp_data->format_len; ++i) {
|
|
if (!(mdp_data->format[i].flags & flag))
|
|
continue;
|
|
if (mdp_data->format[i].pixelformat == pixelformat)
|
|
return &mdp_data->format[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const struct mdp_format *mdp_find_fmt_by_index(const struct mtk_mdp_driver_data *mdp_data,
|
|
u32 index, u32 type)
|
|
{
|
|
u32 i, flag, num = 0;
|
|
|
|
flag = V4L2_TYPE_IS_OUTPUT(type) ? MDP_FMT_FLAG_OUTPUT :
|
|
MDP_FMT_FLAG_CAPTURE;
|
|
for (i = 0; i < mdp_data->format_len; ++i) {
|
|
if (!(mdp_data->format[i].flags & flag))
|
|
continue;
|
|
if (index == num)
|
|
return &mdp_data->format[i];
|
|
num++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
enum mdp_ycbcr_profile mdp_map_ycbcr_prof_mplane(struct v4l2_format *f,
|
|
u32 mdp_color)
|
|
{
|
|
struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
|
|
|
|
if (MDP_COLOR_IS_RGB(mdp_color))
|
|
return MDP_YCBCR_PROFILE_FULL_BT601;
|
|
|
|
switch (pix_mp->colorspace) {
|
|
case V4L2_COLORSPACE_JPEG:
|
|
return MDP_YCBCR_PROFILE_JPEG;
|
|
case V4L2_COLORSPACE_REC709:
|
|
case V4L2_COLORSPACE_DCI_P3:
|
|
if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
|
|
return MDP_YCBCR_PROFILE_FULL_BT709;
|
|
return MDP_YCBCR_PROFILE_BT709;
|
|
case V4L2_COLORSPACE_BT2020:
|
|
if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
|
|
return MDP_YCBCR_PROFILE_FULL_BT2020;
|
|
return MDP_YCBCR_PROFILE_BT2020;
|
|
default:
|
|
if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE)
|
|
return MDP_YCBCR_PROFILE_FULL_BT601;
|
|
return MDP_YCBCR_PROFILE_BT601;
|
|
}
|
|
}
|
|
|
|
static void mdp_bound_align_image(u32 *w, u32 *h,
|
|
struct v4l2_frmsize_stepwise *s,
|
|
unsigned int salign)
|
|
{
|
|
unsigned int org_w, org_h;
|
|
|
|
org_w = *w;
|
|
org_h = *h;
|
|
v4l_bound_align_image(w, s->min_width, s->max_width, s->step_width,
|
|
h, s->min_height, s->max_height, s->step_height,
|
|
salign);
|
|
|
|
s->min_width = org_w;
|
|
s->min_height = org_h;
|
|
v4l2_apply_frmsize_constraints(w, h, s);
|
|
}
|
|
|
|
static int mdp_clamp_align(s32 *x, int min, int max, unsigned int align)
|
|
{
|
|
unsigned int mask;
|
|
|
|
if (min < 0 || max < 0)
|
|
return -ERANGE;
|
|
|
|
/* Bits that must be zero to be aligned */
|
|
mask = ~((1 << align) - 1);
|
|
|
|
min = 0 ? 0 : ((min + ~mask) & mask);
|
|
max = max & mask;
|
|
if ((unsigned int)min > (unsigned int)max)
|
|
return -ERANGE;
|
|
|
|
/* Clamp to aligned min and max */
|
|
*x = clamp(*x, min, max);
|
|
|
|
/* Round to nearest aligned value */
|
|
if (align)
|
|
*x = (*x + (1 << (align - 1))) & mask;
|
|
return 0;
|
|
}
|
|
|
|
int mdp_enum_fmt_mplane(struct mdp_dev *mdp, struct v4l2_fmtdesc *f)
|
|
{
|
|
const struct mdp_format *fmt;
|
|
|
|
fmt = mdp_find_fmt_by_index(mdp->mdp_data, f->index, f->type);
|
|
if (!fmt)
|
|
return -EINVAL;
|
|
|
|
f->pixelformat = fmt->pixelformat;
|
|
return 0;
|
|
}
|
|
|
|
const struct mdp_format *mdp_try_fmt_mplane(struct mdp_dev *mdp,
|
|
struct v4l2_format *f,
|
|
struct mdp_frameparam *param,
|
|
u32 ctx_id)
|
|
{
|
|
struct device *dev = ¶m->ctx->mdp_dev->pdev->dev;
|
|
struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
|
|
const struct mdp_format *fmt;
|
|
const struct mdp_pix_limit *pix_limit;
|
|
struct v4l2_frmsize_stepwise s;
|
|
u32 org_w, org_h;
|
|
unsigned int i;
|
|
|
|
fmt = mdp_find_fmt(mdp->mdp_data, pix_mp->pixelformat, f->type);
|
|
if (!fmt) {
|
|
fmt = mdp_find_fmt_by_index(mdp->mdp_data, 0, f->type);
|
|
if (!fmt) {
|
|
dev_dbg(dev, "%d: pixelformat %c%c%c%c invalid", ctx_id,
|
|
(pix_mp->pixelformat & 0xff),
|
|
(pix_mp->pixelformat >> 8) & 0xff,
|
|
(pix_mp->pixelformat >> 16) & 0xff,
|
|
(pix_mp->pixelformat >> 24) & 0xff);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
pix_mp->field = V4L2_FIELD_NONE;
|
|
pix_mp->flags = 0;
|
|
pix_mp->pixelformat = fmt->pixelformat;
|
|
if (V4L2_TYPE_IS_CAPTURE(f->type)) {
|
|
pix_mp->colorspace = param->colorspace;
|
|
pix_mp->xfer_func = param->xfer_func;
|
|
pix_mp->ycbcr_enc = param->ycbcr_enc;
|
|
pix_mp->quantization = param->quant;
|
|
}
|
|
|
|
pix_limit = V4L2_TYPE_IS_OUTPUT(f->type) ? ¶m->limit->out_limit :
|
|
¶m->limit->cap_limit;
|
|
s.min_width = pix_limit->wmin;
|
|
s.max_width = pix_limit->wmax;
|
|
s.step_width = fmt->walign;
|
|
s.min_height = pix_limit->hmin;
|
|
s.max_height = pix_limit->hmax;
|
|
s.step_height = fmt->halign;
|
|
org_w = pix_mp->width;
|
|
org_h = pix_mp->height;
|
|
|
|
mdp_bound_align_image(&pix_mp->width, &pix_mp->height, &s, fmt->salign);
|
|
if (org_w != pix_mp->width || org_h != pix_mp->height)
|
|
dev_dbg(dev, "%d: size change: %ux%u to %ux%u", ctx_id,
|
|
org_w, org_h, pix_mp->width, pix_mp->height);
|
|
|
|
if (pix_mp->num_planes && pix_mp->num_planes != fmt->num_planes)
|
|
dev_dbg(dev, "%d num of planes change: %u to %u", ctx_id,
|
|
pix_mp->num_planes, fmt->num_planes);
|
|
pix_mp->num_planes = fmt->num_planes;
|
|
|
|
for (i = 0; i < pix_mp->num_planes; ++i) {
|
|
u32 min_bpl = (pix_mp->width * fmt->row_depth[i]) >> 3;
|
|
u32 max_bpl = (pix_limit->wmax * fmt->row_depth[i]) >> 3;
|
|
u32 bpl = pix_mp->plane_fmt[i].bytesperline;
|
|
u32 min_si, max_si;
|
|
u32 si = pix_mp->plane_fmt[i].sizeimage;
|
|
u64 di;
|
|
|
|
bpl = clamp(bpl, min_bpl, max_bpl);
|
|
pix_mp->plane_fmt[i].bytesperline = bpl;
|
|
|
|
di = (u64)bpl * pix_mp->height * fmt->depth[i];
|
|
min_si = (u32)div_u64(di, fmt->row_depth[i]);
|
|
di = (u64)bpl * s.max_height * fmt->depth[i];
|
|
max_si = (u32)div_u64(di, fmt->row_depth[i]);
|
|
|
|
si = clamp(si, min_si, max_si);
|
|
pix_mp->plane_fmt[i].sizeimage = si;
|
|
|
|
dev_dbg(dev, "%d: p%u, bpl:%u [%u, %u], sizeimage:%u [%u, %u]",
|
|
ctx_id, i, bpl, min_bpl, max_bpl, si, min_si, max_si);
|
|
}
|
|
|
|
return fmt;
|
|
}
|
|
|
|
static int mdp_clamp_start(s32 *x, int min, int max, unsigned int align,
|
|
u32 flags)
|
|
{
|
|
if (flags & V4L2_SEL_FLAG_GE)
|
|
max = *x;
|
|
if (flags & V4L2_SEL_FLAG_LE)
|
|
min = *x;
|
|
return mdp_clamp_align(x, min, max, align);
|
|
}
|
|
|
|
static int mdp_clamp_end(s32 *x, int min, int max, unsigned int align,
|
|
u32 flags)
|
|
{
|
|
if (flags & V4L2_SEL_FLAG_GE)
|
|
min = *x;
|
|
if (flags & V4L2_SEL_FLAG_LE)
|
|
max = *x;
|
|
return mdp_clamp_align(x, min, max, align);
|
|
}
|
|
|
|
int mdp_try_crop(struct mdp_m2m_ctx *ctx, struct v4l2_rect *r,
|
|
const struct v4l2_selection *s, struct mdp_frame *frame)
|
|
{
|
|
struct device *dev = &ctx->mdp_dev->pdev->dev;
|
|
s32 left, top, right, bottom;
|
|
u32 framew, frameh, walign, halign;
|
|
int ret;
|
|
|
|
dev_dbg(dev, "%d target:%d, set:(%d,%d) %ux%u", ctx->id,
|
|
s->target, s->r.left, s->r.top, s->r.width, s->r.height);
|
|
|
|
left = s->r.left;
|
|
top = s->r.top;
|
|
right = s->r.left + s->r.width;
|
|
bottom = s->r.top + s->r.height;
|
|
framew = frame->format.fmt.pix_mp.width;
|
|
frameh = frame->format.fmt.pix_mp.height;
|
|
|
|
if (mdp_target_is_crop(s->target)) {
|
|
walign = 1;
|
|
halign = 1;
|
|
} else {
|
|
walign = frame->mdp_fmt->walign;
|
|
halign = frame->mdp_fmt->halign;
|
|
}
|
|
|
|
dev_dbg(dev, "%d align:%u,%u, bound:%ux%u", ctx->id,
|
|
walign, halign, framew, frameh);
|
|
|
|
ret = mdp_clamp_start(&left, 0, right, walign, s->flags);
|
|
if (ret)
|
|
return ret;
|
|
ret = mdp_clamp_start(&top, 0, bottom, halign, s->flags);
|
|
if (ret)
|
|
return ret;
|
|
ret = mdp_clamp_end(&right, left, framew, walign, s->flags);
|
|
if (ret)
|
|
return ret;
|
|
ret = mdp_clamp_end(&bottom, top, frameh, halign, s->flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
r->left = left;
|
|
r->top = top;
|
|
r->width = right - left;
|
|
r->height = bottom - top;
|
|
|
|
dev_dbg(dev, "%d crop:(%d,%d) %ux%u", ctx->id,
|
|
r->left, r->top, r->width, r->height);
|
|
return 0;
|
|
}
|
|
|
|
int mdp_check_scaling_ratio(const struct v4l2_rect *crop,
|
|
const struct v4l2_rect *compose, s32 rotation,
|
|
const struct mdp_limit *limit)
|
|
{
|
|
u32 crop_w, crop_h, comp_w, comp_h;
|
|
|
|
crop_w = crop->width;
|
|
crop_h = crop->height;
|
|
if (90 == rotation || 270 == rotation) {
|
|
comp_w = compose->height;
|
|
comp_h = compose->width;
|
|
} else {
|
|
comp_w = compose->width;
|
|
comp_h = compose->height;
|
|
}
|
|
|
|
if ((crop_w / comp_w) > limit->h_scale_down_max ||
|
|
(crop_h / comp_h) > limit->v_scale_down_max ||
|
|
(comp_w / crop_w) > limit->h_scale_up_max ||
|
|
(comp_h / crop_h) > limit->v_scale_up_max)
|
|
return -ERANGE;
|
|
return 0;
|
|
}
|
|
|
|
/* Stride that is accepted by MDP HW */
|
|
static u32 mdp_fmt_get_stride(const struct mdp_format *fmt,
|
|
u32 bytesperline, unsigned int plane)
|
|
{
|
|
enum mdp_color c = fmt->mdp_color;
|
|
u32 stride;
|
|
|
|
stride = (bytesperline * MDP_COLOR_BITS_PER_PIXEL(c))
|
|
/ fmt->row_depth[0];
|
|
if (plane == 0)
|
|
return stride;
|
|
if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
|
|
if (MDP_COLOR_IS_BLOCK_MODE(c))
|
|
stride = stride / 2;
|
|
return stride;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Stride that is accepted by MDP HW of format with contiguous planes */
|
|
static u32 mdp_fmt_get_stride_contig(const struct mdp_format *fmt,
|
|
u32 pix_stride, unsigned int plane)
|
|
{
|
|
enum mdp_color c = fmt->mdp_color;
|
|
u32 stride = pix_stride;
|
|
|
|
if (plane == 0)
|
|
return stride;
|
|
if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
|
|
stride = stride >> MDP_COLOR_GET_H_SUBSAMPLE(c);
|
|
if (MDP_COLOR_IS_UV_COPLANE(c) && !MDP_COLOR_IS_BLOCK_MODE(c))
|
|
stride = stride * 2;
|
|
return stride;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Plane size that is accepted by MDP HW */
|
|
static u32 mdp_fmt_get_plane_size(const struct mdp_format *fmt,
|
|
u32 stride, u32 height, unsigned int plane)
|
|
{
|
|
enum mdp_color c = fmt->mdp_color;
|
|
u32 bytesperline;
|
|
|
|
bytesperline = (stride * fmt->row_depth[0])
|
|
/ MDP_COLOR_BITS_PER_PIXEL(c);
|
|
if (plane == 0)
|
|
return bytesperline * height;
|
|
if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) {
|
|
height = height >> MDP_COLOR_GET_V_SUBSAMPLE(c);
|
|
if (MDP_COLOR_IS_BLOCK_MODE(c))
|
|
bytesperline = bytesperline * 2;
|
|
return bytesperline * height;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mdp_prepare_buffer(struct img_image_buffer *b,
|
|
struct mdp_frame *frame, struct vb2_buffer *vb)
|
|
{
|
|
struct v4l2_pix_format_mplane *pix_mp = &frame->format.fmt.pix_mp;
|
|
unsigned int i;
|
|
|
|
b->format.colorformat = frame->mdp_fmt->mdp_color;
|
|
b->format.ycbcr_prof = frame->ycbcr_prof;
|
|
for (i = 0; i < pix_mp->num_planes; ++i) {
|
|
u32 stride = mdp_fmt_get_stride(frame->mdp_fmt,
|
|
pix_mp->plane_fmt[i].bytesperline, i);
|
|
|
|
b->format.plane_fmt[i].stride = stride;
|
|
b->format.plane_fmt[i].size =
|
|
mdp_fmt_get_plane_size(frame->mdp_fmt, stride,
|
|
pix_mp->height, i);
|
|
b->iova[i] = vb2_dma_contig_plane_dma_addr(vb, i);
|
|
}
|
|
for (; i < MDP_COLOR_GET_PLANE_COUNT(b->format.colorformat); ++i) {
|
|
u32 stride = mdp_fmt_get_stride_contig(frame->mdp_fmt,
|
|
b->format.plane_fmt[0].stride, i);
|
|
|
|
b->format.plane_fmt[i].stride = stride;
|
|
b->format.plane_fmt[i].size =
|
|
mdp_fmt_get_plane_size(frame->mdp_fmt, stride,
|
|
pix_mp->height, i);
|
|
b->iova[i] = b->iova[i - 1] + b->format.plane_fmt[i - 1].size;
|
|
}
|
|
b->usage = frame->usage;
|
|
}
|
|
|
|
void mdp_set_src_config(struct img_input *in,
|
|
struct mdp_frame *frame, struct vb2_buffer *vb)
|
|
{
|
|
in->buffer.format.width = frame->format.fmt.pix_mp.width;
|
|
in->buffer.format.height = frame->format.fmt.pix_mp.height;
|
|
mdp_prepare_buffer(&in->buffer, frame, vb);
|
|
}
|
|
|
|
static u32 mdp_to_fixed(u32 *r, struct v4l2_fract *f)
|
|
{
|
|
u32 q;
|
|
|
|
if (f->denominator == 0) {
|
|
*r = 0;
|
|
return 0;
|
|
}
|
|
|
|
q = f->numerator / f->denominator;
|
|
*r = div_u64(((u64)f->numerator - q * f->denominator) <<
|
|
IMG_SUBPIXEL_SHIFT, f->denominator);
|
|
return q;
|
|
}
|
|
|
|
static void mdp_set_src_crop(struct img_crop *c, struct mdp_crop *crop)
|
|
{
|
|
c->left = crop->c.left
|
|
+ mdp_to_fixed(&c->left_subpix, &crop->left_subpix);
|
|
c->top = crop->c.top
|
|
+ mdp_to_fixed(&c->top_subpix, &crop->top_subpix);
|
|
c->width = crop->c.width
|
|
+ mdp_to_fixed(&c->width_subpix, &crop->width_subpix);
|
|
c->height = crop->c.height
|
|
+ mdp_to_fixed(&c->height_subpix, &crop->height_subpix);
|
|
}
|
|
|
|
static void mdp_set_orientation(struct img_output *out,
|
|
s32 rotation, bool hflip, bool vflip)
|
|
{
|
|
u8 flip = 0;
|
|
|
|
if (hflip)
|
|
flip ^= 1;
|
|
if (vflip) {
|
|
/*
|
|
* A vertical flip is equivalent to
|
|
* a 180-degree rotation with a horizontal flip
|
|
*/
|
|
rotation += 180;
|
|
flip ^= 1;
|
|
}
|
|
|
|
out->rotation = rotation % 360;
|
|
if (flip != 0)
|
|
out->flags |= IMG_CTRL_FLAG_HFLIP;
|
|
else
|
|
out->flags &= ~IMG_CTRL_FLAG_HFLIP;
|
|
}
|
|
|
|
void mdp_set_dst_config(struct img_output *out,
|
|
struct mdp_frame *frame, struct vb2_buffer *vb)
|
|
{
|
|
out->buffer.format.width = frame->compose.width;
|
|
out->buffer.format.height = frame->compose.height;
|
|
mdp_prepare_buffer(&out->buffer, frame, vb);
|
|
mdp_set_src_crop(&out->crop, &frame->crop);
|
|
mdp_set_orientation(out, frame->rotation, frame->hflip, frame->vflip);
|
|
}
|
|
|
|
int mdp_frameparam_init(struct mdp_dev *mdp, struct mdp_frameparam *param)
|
|
{
|
|
struct mdp_frame *frame;
|
|
|
|
if (!param)
|
|
return -EINVAL;
|
|
|
|
INIT_LIST_HEAD(¶m->list);
|
|
param->limit = mdp->mdp_data->def_limit;
|
|
param->type = MDP_STREAM_TYPE_BITBLT;
|
|
|
|
frame = ¶m->output;
|
|
frame->format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
|
frame->mdp_fmt = mdp_try_fmt_mplane(mdp, &frame->format, param, 0);
|
|
frame->ycbcr_prof =
|
|
mdp_map_ycbcr_prof_mplane(&frame->format,
|
|
frame->mdp_fmt->mdp_color);
|
|
frame->usage = MDP_BUFFER_USAGE_HW_READ;
|
|
|
|
param->num_captures = 1;
|
|
frame = ¶m->captures[0];
|
|
frame->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
|
frame->mdp_fmt = mdp_try_fmt_mplane(mdp, &frame->format, param, 0);
|
|
frame->ycbcr_prof =
|
|
mdp_map_ycbcr_prof_mplane(&frame->format,
|
|
frame->mdp_fmt->mdp_color);
|
|
frame->usage = MDP_BUFFER_USAGE_MDP;
|
|
frame->crop.c.width = param->output.format.fmt.pix_mp.width;
|
|
frame->crop.c.height = param->output.format.fmt.pix_mp.height;
|
|
frame->compose.width = frame->format.fmt.pix_mp.width;
|
|
frame->compose.height = frame->format.fmt.pix_mp.height;
|
|
|
|
return 0;
|
|
}
|