// SPDX-License-Identifier: GPL-2.0+ /* * Renesas R-Car Fine Display Processor * * Video format converter and frame deinterlacer device. * * Author: Kieran Bingham, <kieran@bingham.xyz> * Copyright (c) 2016 Renesas Electronics Corporation. * * This code is developed and inspired from the vim2m, rcar_jpu, * m2m-deinterlace, and vsp1 drivers.
*/
/** * struct fdp1_fmt - The FDP1 internal format data * @fourcc: the fourcc code, to match the V4L2 API * @bpp: bits per pixel per plane * @num_planes: number of planes * @hsub: horizontal subsampling factor * @vsub: vertical subsampling factor * @fmt: 7-bit format code for the fdp1 hardware * @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) * @swap: swap register control * @types: types of queue this format is applicable to
*/ struct fdp1_fmt {
u32 fourcc;
u8 bpp[3];
u8 num_planes;
u8 hsub;
u8 vsub;
u8 fmt; bool swap_yc; bool swap_uv;
u8 swap;
u8 types;
};
staticconststruct fdp1_fmt fdp1_formats[] = { /* RGB formats are only supported by the Write Pixel Formatter */
/* * FDP1 Lookup tables range from 0...255 only * * Each table must be less than 256 entries, and all tables * are padded out to 256 entries by duplicating the last value.
*/ staticconst u8 fdp1_diff_adj[] = {
0x00, 0x24, 0x43, 0x5e, 0x76, 0x8c, 0x9e, 0xaf,
0xbd, 0xc9, 0xd4, 0xdd, 0xe4, 0xea, 0xef, 0xf3,
0xf6, 0xf9, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xff,
};
for (i = 0; i < ARRAY_SIZE(fdp1_formats); i++) {
fmt = &fdp1_formats[i]; if (fmt->fourcc == pixelformat) return fmt;
}
return NULL;
}
enum fdp1_deint_mode {
FDP1_PROGRESSIVE = 0, /* Must be zero when !deinterlacing */
FDP1_ADAPT2D3D,
FDP1_FIXED2D,
FDP1_FIXED3D,
FDP1_PREVFIELD,
FDP1_NEXTFIELD,
};
/* * FDP1 operates on potentially 3 fields, which are tracked * from the VB buffers using this context structure. * Will always be a field or a full frame, never two fields.
*/ struct fdp1_field_buffer { struct vb2_v4l2_buffer *vb;
dma_addr_t addrs[3];
/* Should be NONE:TOP:BOTTOM only */ enum v4l2_field field;
/* Flag to indicate this is the last field in the vb */ bool last_field;
/* * Adaptive 2D/3D mode uses a shared mask * This is allocated at streamon, if the ADAPT2D3D mode * is requested
*/ unsignedint smsk_size;
dma_addr_t smsk_addr[2]; void *smsk_cpu;
/* Capture pipeline, can specify an alpha value * for supported formats. 0-255 only
*/ unsignedchar alpha;
/* * Field Queues * Interlaced fields are used on 3 occasions, and tracked in this list. * * V4L2 Buffers are tracked inside the fdp1_buffer * and released when the last 'field' completes
*/ struct list_head fields_queue; unsignedint buffers_queued;
/* * For de-interlacing we need to track our previous buffer * while preparing our job lists.
*/ struct fdp1_field_buffer *previous;
};
/* * list_remove_job: Take the first item off the specified job list * * Returns: pointer to a job, or NULL if the list is empty.
*/ staticstruct fdp1_job *list_remove_job(struct fdp1_dev *fdp1, struct list_head *list)
{ struct fdp1_job *job; unsignedlong flags;
staticvoid fdp1_job_free(struct fdp1_dev *fdp1, struct fdp1_job *job)
{ /* Ensure that all residue from previous jobs is gone */
memset(job, 0, sizeof(struct fdp1_job));
/* * Buffer lists handling
*/ staticvoid fdp1_field_complete(struct fdp1_ctx *ctx, struct fdp1_field_buffer *fbuf)
{ /* job->previous may be on the first field */ if (!fbuf) return;
if (fbuf->last_field)
v4l2_m2m_buf_done(fbuf->vb, VB2_BUF_STATE_DONE);
}
/* * Return the next field in the queue - or NULL, * without removing the item from the list
*/ staticstruct fdp1_field_buffer *fdp1_peek_queued_field(struct fdp1_ctx *ctx)
{ struct fdp1_field_buffer *fbuf; unsignedlong flags;
/* * fdp1_write_lut: Write a padded LUT to the hw * * FDP1 uses constant data for de-interlacing processing, * with large tables. These hardware tables are all 256 bytes * long, however they often contain repeated data at the end. * * The last byte of the table is written to all remaining entries.
*/ staticvoid fdp1_write_lut(struct fdp1_dev *fdp1, const u8 *lut, unsignedint len, unsignedint base)
{ unsignedint i;
u8 pad;
/* Tables larger than the hw are clipped */
len = min(len, 256u);
for (i = 0; i < len; i++)
fdp1_write(fdp1, lut[i], base + (i*4));
/* Tables are padded with the last entry */
pad = lut[i-1];
for (; i < 256; i++)
fdp1_write(fdp1, pad, base + (i*4));
}
/* Picture size is common to Source and Destination frames */
picture_size = (q_data->format.width << FD1_RPF_SIZE_H_SHIFT)
| (q_data->vsize << FD1_RPF_SIZE_V_SHIFT);
/* Format control */
format = q_data->fmt->fmt; if (q_data->fmt->swap_yc)
format |= FD1_RPF_FORMAT_RSPYCS;
if (q_data->fmt->swap_uv)
format |= FD1_RPF_FORMAT_RSPUVS;
if (job->active->field == V4L2_FIELD_BOTTOM) {
format |= FD1_RPF_FORMAT_CF; /* Set for Bottom field */
smsk_addr = ctx->smsk_addr[0];
} else {
smsk_addr = ctx->smsk_addr[1];
}
/* Deint mode is non-zero when deinterlacing */ if (ctx->deint_mode)
format |= FD1_RPF_FORMAT_CIPM;
if (q_data->format.num_planes > 1)
pstride |= q_data->format.plane_fmt[1].bytesperline
<< FD1_WPF_PSTRIDE_C_SHIFT;
format = q_data->fmt->fmt; /* Output Format Code */
if (q_data->fmt->swap_yc)
format |= FD1_WPF_FORMAT_WSPYCS;
if (q_data->fmt->swap_uv)
format |= FD1_WPF_FORMAT_WSPUVS;
if (fdp1_fmt_is_rgb(q_data->fmt)) { /* Enable Colour Space conversion */
format |= FD1_WPF_FORMAT_CSC;
/* Set WRTM */ if (src_q_data->format.ycbcr_enc == V4L2_YCBCR_ENC_709)
format |= FD1_WPF_FORMAT_WRTM_709_16; elseif (src_q_data->format.quantization ==
V4L2_QUANTIZATION_FULL_RANGE)
format |= FD1_WPF_FORMAT_WRTM_601_0; else
format |= FD1_WPF_FORMAT_WRTM_601_16;
}
/* Set an alpha value into the Pad Value */
format |= ctx->alpha << FD1_WPF_FORMAT_PDV_SHIFT;
/* Determine picture rounding and clipping */
rndctl = FD1_WPF_RNDCTL_CBRM; /* Rounding Off */
rndctl |= FD1_WPF_RNDCTL_CLMD_NOCLIP;
/* WPF Swap needs both ISWAP and OSWAP setting */
swap = q_data->fmt->swap << FD1_WPF_SWAP_OSWAP_SHIFT;
swap |= src_q_data->fmt->swap << FD1_WPF_SWAP_SSWAP_SHIFT;
/* * fdp1_device_process() - Run the hardware * * Configure and start the hardware to generate a single frame * of output given our input parameters.
*/ staticint fdp1_device_process(struct fdp1_ctx *ctx)
/* Get a job to process */
job = get_queued_job(fdp1); if (!job) { /* * VINT can call us to see if we can queue another job. * If we have no work to do, we simply return.
*/
spin_unlock_irqrestore(&fdp1->device_process_lock, flags); return 0;
}
/* First Frame only? ... */
fdp1_write(fdp1, FD1_CTL_CLKCTRL_CSTP_N, FD1_CTL_CLKCTRL);
/* Set the mode, and configuration */
fdp1_configure_deint_mode(ctx, job);
/* * job_ready() - check whether an instance is ready to be scheduled to run
*/ staticint fdp1_m2m_job_ready(void *priv)
{ struct fdp1_ctx *ctx = priv; struct fdp1_q_data *src_q_data = &ctx->out_q; int srcbufs = 1; int dstbufs = 1;
/* * fdp1_prepare_job: Prepare and queue a new job for a single action of work * * Prepare the next field, (or frame in progressive) and an output * buffer for the hardware to perform a single operation.
*/ staticstruct fdp1_job *fdp1_prepare_job(struct fdp1_ctx *ctx)
{ struct vb2_v4l2_buffer *vbuf; struct fdp1_buffer *fbuf; struct fdp1_dev *fdp1 = ctx->fdp1; struct fdp1_job *job; unsignedint buffers_required = 1;
dprintk(fdp1, "+\n");
if (FDP1_DEINT_MODE_USES_NEXT(ctx->deint_mode))
buffers_required = 2;
if (ctx->buffers_queued < buffers_required) return NULL;
job = fdp1_job_alloc(fdp1); if (!job) {
dprintk(fdp1, "No free jobs currently available\n"); return NULL;
}
job->active = fdp1_dequeue_field(ctx); if (!job->active) { /* Buffer check should prevent this ever happening */
dprintk(fdp1, "No input buffers currently available\n");
fdp1_job_free(fdp1, job); return NULL;
}
dprintk(fdp1, "+ Buffer en-route...\n");
/* Source buffers have been prepared on our buffer_queue * Prepare our Output buffer
*/
vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
fbuf = to_fdp1_buffer(vbuf);
job->dst = &fbuf->fields[0];
/* fdp1_m2m_device_run() - prepares and starts the device for an M2M task * * A single input buffer is taken and serialised into our fdp1_buffer * queue. The queue is then processed to create as many jobs as possible * from our available input.
*/ staticvoid fdp1_m2m_device_run(void *priv)
{ struct fdp1_ctx *ctx = priv; struct fdp1_dev *fdp1 = ctx->fdp1; struct vb2_v4l2_buffer *src_vb; struct fdp1_buffer *buf; unsignedint i;
dprintk(fdp1, "+\n");
ctx->translen = 0;
/* Get our incoming buffer of either one or two fields, or one frame */
src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
buf = to_fdp1_buffer(src_vb);
for (i = 0; i < buf->num_fields; i++) { struct fdp1_field_buffer *fbuf = &buf->fields[i];
fdp1_queue_field(ctx, fbuf);
dprintk(fdp1, "Queued Buffer [%d] last_field:%d\n",
i, fbuf->last_field);
}
/* Queue as many jobs as our data provides for */ while (fdp1_prepare_job(ctx))
;
if (ctx->translen == 0) {
dprintk(fdp1, "No jobs were processed. M2M action complete\n");
v4l2_m2m_job_finish(fdp1->m2m_dev, ctx->fh.m2m_ctx); return;
}
/* Kick the job processing action */
fdp1_device_process(ctx);
}
if (ctx == NULL) {
v4l2_err(&fdp1->v4l2_dev, "Instance released before the end of transaction\n"); return;
}
ctx->num_processed++;
/* * fdp1_field_complete will call buf_done only when the last vb2_buffer * reference is complete
*/ if (FDP1_DEINT_MODE_USES_PREV(ctx->deint_mode))
fdp1_field_complete(ctx, job->previous); else
fdp1_field_complete(ctx, job->active);
if (ctx->num_processed == ctx->translen ||
ctx->aborting) {
dprintk(ctx->fdp1, "Finishing transaction\n");
ctx->num_processed = 0;
v4l2_m2m_job_finish(fdp1->m2m_dev, ctx->fh.m2m_ctx);
} else { /* * For pipelined performance support, this would * be called from a VINT handler
*/
fdp1_device_process(ctx);
}
}
if (fmt->num_planes == 3) { /* The two chroma planes must have the same stride. */
pix->plane_fmt[2].bytesperline = pix->plane_fmt[1].bytesperline;
pix->plane_fmt[2].sizeimage = pix->plane_fmt[1].sizeimage;
/* Validate the pixel format to ensure the output queue supports it. */
fmt = fdp1_find_format(pix->pixelformat); if (!fmt || !(fmt->types & FDP1_OUTPUT))
fmt = fdp1_find_format(V4L2_PIX_FMT_YUYV);
/* * Progressive video and all interlaced field orders are acceptable. * Default to V4L2_FIELD_INTERLACED.
*/ if (pix->field != V4L2_FIELD_NONE &&
pix->field != V4L2_FIELD_ALTERNATE &&
!V4L2_FIELD_HAS_BOTH(pix->field))
pix->field = V4L2_FIELD_INTERLACED;
/* * The deinterlacer doesn't care about the colorspace, accept all values * and default to V4L2_COLORSPACE_SMPTE170M. The YUV to RGB conversion * at the output of the deinterlacer supports a subset of encodings and * quantization methods and will only be available when the colorspace * allows it.
*/ if (pix->colorspace == V4L2_COLORSPACE_DEFAULT)
pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
/* * Align the width and height for YUV 4:2:2 and 4:2:0 formats and clamp * them to the supported frame size range. The height boundary are * related to the full frame, divide them by two when the format passes * fields in separate buffers.
*/
width = round_down(pix->width, fmt->hsub);
pix->width = clamp(width, FDP1_MIN_W, FDP1_MAX_W);
/* * Validate the pixel format. We can only accept RGB output formats if * the input encoding and quantization are compatible with the format * conversions supported by the hardware. The supported combinations are * * V4L2_YCBCR_ENC_601 + V4L2_QUANTIZATION_LIM_RANGE * V4L2_YCBCR_ENC_601 + V4L2_QUANTIZATION_FULL_RANGE * V4L2_YCBCR_ENC_709 + V4L2_QUANTIZATION_LIM_RANGE
*/
colorspace = src_data->format.colorspace;
ycbcr_enc = src_data->format.ycbcr_enc; if (ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(colorspace);
/* * The colorspace on the capture queue is copied from the output queue * as the hardware can't change the colorspace. It can convert YCbCr to * RGB though, in which case the encoding and quantization are set to * default values as anything else wouldn't make sense.
*/
pix->colorspace = src_data->format.colorspace;
pix->xfer_func = src_data->format.xfer_func;
/* * The frame width is identical to the output queue, and the height is * either doubled or identical depending on whether the output queue * field order contains one or two fields per frame.
*/
pix->width = src_data->format.width; if (src_data->format.field == V4L2_FIELD_ALTERNATE)
pix->height = 2 * src_data->format.height; else
pix->height = src_data->format.height;
/* Propagate the format from the output node to the capture node. */ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { struct fdp1_q_data *dst_data = &ctx->cap_q;
/* * Copy the format, clear the per-plane bytes per line and image * size, override the field and double the height if needed.
*/
dst_data->format = q_data->format;
memset(dst_data->format.plane_fmt, 0, sizeof(dst_data->format.plane_fmt));
dst_data->format.field = V4L2_FIELD_NONE; if (pix->field == V4L2_FIELD_ALTERNATE)
dst_data->format.height *= 2;
for (i = 0; i < vbuf->vb2_buf.num_planes; ++i)
fbuf->addrs[i] = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, i);
switch (vbuf->field) { case V4L2_FIELD_INTERLACED: /* * Interlaced means bottom-top for 60Hz TV standards (NTSC) and * top-bottom for 50Hz. As TV standards are not applicable to * the mem-to-mem API, use the height as a heuristic.
*/
fbuf->field = (q_data->format.height < 576) == field_num
? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; break; case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_SEQ_TB:
fbuf->field = field_num ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP; break; case V4L2_FIELD_INTERLACED_BT: case V4L2_FIELD_SEQ_BT:
fbuf->field = field_num ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; break; default:
fbuf->field = vbuf->field; break;
}
/* Buffer is completed */ if (!field_num) return;
/* Adjust buffer addresses for second field */ switch (vbuf->field) { case V4L2_FIELD_INTERLACED: case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: for (i = 0; i < vbuf->vb2_buf.num_planes; i++)
fbuf->addrs[i] +=
(i == 0 ? q_data->stride_y : q_data->stride_c); break; case V4L2_FIELD_SEQ_TB: case V4L2_FIELD_SEQ_BT: for (i = 0; i < vbuf->vb2_buf.num_planes; i++)
fbuf->addrs[i] += q_data->vsize *
(i == 0 ? q_data->stride_y : q_data->stride_c); break;
}
}
if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { bool field_valid = true;
/* Validate the buffer field. */ switch (q_data->format.field) { case V4L2_FIELD_NONE: if (vbuf->field != V4L2_FIELD_NONE)
field_valid = false; break;
case V4L2_FIELD_ALTERNATE: if (vbuf->field != V4L2_FIELD_TOP &&
vbuf->field != V4L2_FIELD_BOTTOM)
field_valid = false; break;
case V4L2_FIELD_INTERLACED: case V4L2_FIELD_SEQ_TB: case V4L2_FIELD_SEQ_BT: case V4L2_FIELD_INTERLACED_TB: case V4L2_FIELD_INTERLACED_BT: if (vbuf->field != q_data->format.field)
field_valid = false; break;
}
if (!field_valid) {
dprintk(ctx->fdp1, "buffer field %u invalid for format field %u\n",
vbuf->field, q_data->format.field); return -EINVAL;
}
} else {
vbuf->field = V4L2_FIELD_NONE;
}
/* Validate the planes sizes. */ for (i = 0; i < q_data->format.num_planes; i++) { unsignedlong size = q_data->format.plane_fmt[i].sizeimage;
if (vb2_plane_size(vb, i) < size) {
dprintk(ctx->fdp1, "data will not fit into plane [%u/%u] (%lu < %lu)\n",
i, q_data->format.num_planes,
vb2_plane_size(vb, i), size); return -EINVAL;
}
/* We have known size formats all around */
vb2_set_plane_payload(vb, i, size);
}
buf->num_fields = V4L2_FIELD_HAS_BOTH(vbuf->field) ? 2 : 1; for (i = 0; i < buf->num_fields; ++i)
fdp1_buf_prepare_field(q_data, vbuf, i);
if (V4L2_TYPE_IS_OUTPUT(q->type)) { /* * Force our deint_mode when we are progressive, * ignoring any setting on the device from the user, * Otherwise, lock in the requested de-interlace mode.
*/ if (q_data->format.field == V4L2_FIELD_NONE)
ctx->deint_mode = FDP1_PROGRESSIVE;
if (ctx->deint_mode == FDP1_ADAPT2D3D) {
u32 stride;
dma_addr_t smsk_base; const u32 bpp = 2; /* bytes per pixel */
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.