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,6 @@
# SPDX-License-Identifier: GPL-2.0-only
comment "STMicroelectronics media platform drivers"
source "drivers/media/platform/st/sti/Kconfig"
source "drivers/media/platform/st/stm32/Kconfig"
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += sti/bdisp/
obj-y += sti/delta/
obj-y += sti/hva/
obj-y += stm32/
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
source "drivers/media/platform/st/sti/bdisp/Kconfig"
source "drivers/media/platform/st/sti/delta/Kconfig"
source "drivers/media/platform/st/sti/hva/Kconfig"
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += bdisp/
obj-y += delta/
obj-y += hva/
obj-y += stm32/
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_STI_BDISP
tristate "STMicroelectronics BDISP 2D blitter driver"
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_STI || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select V4L2_MEM2MEM_DEV
help
This v4l2 mem2mem driver is a 2D blitter for STMicroelectronics SoC.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO_STI_BDISP) += bdisp.o
bdisp-objs := bdisp-v4l2.o bdisp-hw.o bdisp-debug.o
@@ -0,0 +1,658 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#include "bdisp.h"
#include "bdisp-filter.h"
#include "bdisp-reg.h"
void bdisp_dbg_perf_begin(struct bdisp_dev *bdisp)
{
bdisp->dbg.hw_start = ktime_get();
}
void bdisp_dbg_perf_end(struct bdisp_dev *bdisp)
{
s64 time_us;
time_us = ktime_us_delta(ktime_get(), bdisp->dbg.hw_start);
if (!bdisp->dbg.min_duration)
bdisp->dbg.min_duration = time_us;
else
bdisp->dbg.min_duration = min(time_us, bdisp->dbg.min_duration);
bdisp->dbg.last_duration = time_us;
bdisp->dbg.max_duration = max(time_us, bdisp->dbg.max_duration);
bdisp->dbg.tot_duration += time_us;
}
static void bdisp_dbg_dump_ins(struct seq_file *s, u32 val)
{
seq_printf(s, "INS\t0x%08X\t", val);
switch (val & BLT_INS_S1_MASK) {
case BLT_INS_S1_OFF:
break;
case BLT_INS_S1_MEM:
seq_puts(s, "SRC1=mem - ");
break;
case BLT_INS_S1_CF:
seq_puts(s, "SRC1=ColorFill - ");
break;
case BLT_INS_S1_COPY:
seq_puts(s, "SRC1=copy - ");
break;
case BLT_INS_S1_FILL:
seq_puts(s, "SRC1=fil - ");
break;
default:
seq_puts(s, "SRC1=??? - ");
break;
}
switch (val & BLT_INS_S2_MASK) {
case BLT_INS_S2_OFF:
break;
case BLT_INS_S2_MEM:
seq_puts(s, "SRC2=mem - ");
break;
case BLT_INS_S2_CF:
seq_puts(s, "SRC2=ColorFill - ");
break;
default:
seq_puts(s, "SRC2=??? - ");
break;
}
if ((val & BLT_INS_S3_MASK) == BLT_INS_S3_MEM)
seq_puts(s, "SRC3=mem - ");
if (val & BLT_INS_IVMX)
seq_puts(s, "IVMX - ");
if (val & BLT_INS_CLUT)
seq_puts(s, "CLUT - ");
if (val & BLT_INS_SCALE)
seq_puts(s, "Scale - ");
if (val & BLT_INS_FLICK)
seq_puts(s, "Flicker - ");
if (val & BLT_INS_CLIP)
seq_puts(s, "Clip - ");
if (val & BLT_INS_CKEY)
seq_puts(s, "ColorKey - ");
if (val & BLT_INS_OVMX)
seq_puts(s, "OVMX - ");
if (val & BLT_INS_DEI)
seq_puts(s, "Deint - ");
if (val & BLT_INS_PMASK)
seq_puts(s, "PlaneMask - ");
if (val & BLT_INS_VC1R)
seq_puts(s, "VC1R - ");
if (val & BLT_INS_ROTATE)
seq_puts(s, "Rotate - ");
if (val & BLT_INS_GRAD)
seq_puts(s, "GradFill - ");
if (val & BLT_INS_AQLOCK)
seq_puts(s, "AQLock - ");
if (val & BLT_INS_PACE)
seq_puts(s, "Pace - ");
if (val & BLT_INS_IRQ)
seq_puts(s, "IRQ - ");
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_tty(struct seq_file *s, u32 val)
{
seq_printf(s, "TTY\t0x%08X\t", val);
seq_printf(s, "Pitch=%d - ", val & 0xFFFF);
switch ((val & BLT_TTY_COL_MASK) >> BLT_TTY_COL_SHIFT) {
case BDISP_RGB565:
seq_puts(s, "RGB565 - ");
break;
case BDISP_RGB888:
seq_puts(s, "RGB888 - ");
break;
case BDISP_XRGB8888:
seq_puts(s, "xRGB888 - ");
break;
case BDISP_ARGB8888:
seq_puts(s, "ARGB8888 - ");
break;
case BDISP_NV12:
seq_puts(s, "NV12 - ");
break;
case BDISP_YUV_3B:
seq_puts(s, "YUV420P - ");
break;
default:
seq_puts(s, "ColorFormat ??? - ");
break;
}
if (val & BLT_TTY_ALPHA_R)
seq_puts(s, "AlphaRange - ");
if (val & BLT_TTY_CR_NOT_CB)
seq_puts(s, "CrNotCb - ");
if (val & BLT_TTY_MB)
seq_puts(s, "MB - ");
if (val & BLT_TTY_HSO)
seq_puts(s, "HSO inverse - ");
if (val & BLT_TTY_VSO)
seq_puts(s, "VSO inverse - ");
if (val & BLT_TTY_DITHER)
seq_puts(s, "Dither - ");
if (val & BLT_TTY_CHROMA)
seq_puts(s, "Write CHROMA - ");
if (val & BLT_TTY_BIG_END)
seq_puts(s, "BigEndian - ");
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_xy(struct seq_file *s, u32 val, char *name)
{
seq_printf(s, "%s\t0x%08X\t", name, val);
seq_printf(s, "(%d,%d)\n", val & 0xFFFF, (val >> 16));
}
static void bdisp_dbg_dump_sz(struct seq_file *s, u32 val, char *name)
{
seq_printf(s, "%s\t0x%08X\t", name, val);
seq_printf(s, "%dx%d\n", val & 0x1FFF, (val >> 16) & 0x1FFF);
}
static void bdisp_dbg_dump_sty(struct seq_file *s,
u32 val, u32 addr, char *name)
{
bool s1, s2, s3;
seq_printf(s, "%s\t0x%08X\t", name, val);
if (!addr || !name || (strlen(name) < 2))
goto done;
s1 = name[strlen(name) - 1] == '1';
s2 = name[strlen(name) - 1] == '2';
s3 = name[strlen(name) - 1] == '3';
seq_printf(s, "Pitch=%d - ", val & 0xFFFF);
switch ((val & BLT_TTY_COL_MASK) >> BLT_TTY_COL_SHIFT) {
case BDISP_RGB565:
seq_puts(s, "RGB565 - ");
break;
case BDISP_RGB888:
seq_puts(s, "RGB888 - ");
break;
case BDISP_XRGB8888:
seq_puts(s, "xRGB888 - ");
break;
case BDISP_ARGB8888:
seq_puts(s, "ARGB888 - ");
break;
case BDISP_NV12:
seq_puts(s, "NV12 - ");
break;
case BDISP_YUV_3B:
seq_puts(s, "YUV420P - ");
break;
default:
seq_puts(s, "ColorFormat ??? - ");
break;
}
if ((val & BLT_TTY_ALPHA_R) && !s3)
seq_puts(s, "AlphaRange - ");
if ((val & BLT_S1TY_A1_SUBSET) && !s3)
seq_puts(s, "A1SubSet - ");
if ((val & BLT_TTY_MB) && !s1)
seq_puts(s, "MB - ");
if (val & BLT_TTY_HSO)
seq_puts(s, "HSO inverse - ");
if (val & BLT_TTY_VSO)
seq_puts(s, "VSO inverse - ");
if ((val & BLT_S1TY_CHROMA_EXT) && (s1 || s2))
seq_puts(s, "ChromaExt - ");
if ((val & BLT_S3TY_BLANK_ACC) && s3)
seq_puts(s, "Blank Acc - ");
if ((val & BTL_S1TY_SUBBYTE) && !s3)
seq_puts(s, "SubByte - ");
if ((val & BLT_S1TY_RGB_EXP) && !s3)
seq_puts(s, "RGBExpand - ");
if ((val & BLT_TTY_BIG_END) && !s3)
seq_puts(s, "BigEndian - ");
done:
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_fctl(struct seq_file *s, u32 val)
{
seq_printf(s, "FCTL\t0x%08X\t", val);
if ((val & BLT_FCTL_Y_HV_SCALE) == BLT_FCTL_Y_HV_SCALE)
seq_puts(s, "Resize Luma - ");
else if ((val & BLT_FCTL_Y_HV_SCALE) == BLT_FCTL_Y_HV_SAMPLE)
seq_puts(s, "Sample Luma - ");
if ((val & BLT_FCTL_HV_SCALE) == BLT_FCTL_HV_SCALE)
seq_puts(s, "Resize Chroma");
else if ((val & BLT_FCTL_HV_SCALE) == BLT_FCTL_HV_SAMPLE)
seq_puts(s, "Sample Chroma");
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_rsf(struct seq_file *s, u32 val, char *name)
{
u32 inc;
seq_printf(s, "%s\t0x%08X\t", name, val);
if (!val)
goto done;
inc = val & 0xFFFF;
seq_printf(s, "H: %d(6.10) / scale~%dx0.1 - ", inc, 1024 * 10 / inc);
inc = val >> 16;
seq_printf(s, "V: %d(6.10) / scale~%dx0.1", inc, 1024 * 10 / inc);
done:
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_rzi(struct seq_file *s, u32 val, char *name)
{
seq_printf(s, "%s\t0x%08X\t", name, val);
if (!val)
goto done;
seq_printf(s, "H: init=%d repeat=%d - ", val & 0x3FF, (val >> 12) & 7);
val >>= 16;
seq_printf(s, "V: init=%d repeat=%d", val & 0x3FF, (val >> 12) & 7);
done:
seq_putc(s, '\n');
}
static void bdisp_dbg_dump_ivmx(struct seq_file *s,
u32 c0, u32 c1, u32 c2, u32 c3)
{
seq_printf(s, "IVMX0\t0x%08X\n", c0);
seq_printf(s, "IVMX1\t0x%08X\n", c1);
seq_printf(s, "IVMX2\t0x%08X\n", c2);
seq_printf(s, "IVMX3\t0x%08X\t", c3);
if (!c0 && !c1 && !c2 && !c3) {
seq_putc(s, '\n');
return;
}
if ((c0 == bdisp_rgb_to_yuv[0]) &&
(c1 == bdisp_rgb_to_yuv[1]) &&
(c2 == bdisp_rgb_to_yuv[2]) &&
(c3 == bdisp_rgb_to_yuv[3])) {
seq_puts(s, "RGB to YUV\n");
return;
}
if ((c0 == bdisp_yuv_to_rgb[0]) &&
(c1 == bdisp_yuv_to_rgb[1]) &&
(c2 == bdisp_yuv_to_rgb[2]) &&
(c3 == bdisp_yuv_to_rgb[3])) {
seq_puts(s, "YUV to RGB\n");
return;
}
seq_puts(s, "Unknown conversion\n");
}
static int last_nodes_show(struct seq_file *s, void *data)
{
/* Not dumping all fields, focusing on significant ones */
struct bdisp_dev *bdisp = s->private;
struct bdisp_node *node;
int i = 0;
if (!bdisp->dbg.copy_node[0]) {
seq_puts(s, "No node built yet\n");
return 0;
}
do {
node = bdisp->dbg.copy_node[i];
if (!node)
break;
seq_printf(s, "--------\nNode %d:\n", i);
seq_puts(s, "-- General --\n");
seq_printf(s, "NIP\t0x%08X\n", node->nip);
seq_printf(s, "CIC\t0x%08X\n", node->cic);
bdisp_dbg_dump_ins(s, node->ins);
seq_printf(s, "ACK\t0x%08X\n", node->ack);
seq_puts(s, "-- Target --\n");
seq_printf(s, "TBA\t0x%08X\n", node->tba);
bdisp_dbg_dump_tty(s, node->tty);
bdisp_dbg_dump_xy(s, node->txy, "TXY");
bdisp_dbg_dump_sz(s, node->tsz, "TSZ");
/* Color Fill not dumped */
seq_puts(s, "-- Source 1 --\n");
seq_printf(s, "S1BA\t0x%08X\n", node->s1ba);
bdisp_dbg_dump_sty(s, node->s1ty, node->s1ba, "S1TY");
bdisp_dbg_dump_xy(s, node->s1xy, "S1XY");
seq_puts(s, "-- Source 2 --\n");
seq_printf(s, "S2BA\t0x%08X\n", node->s2ba);
bdisp_dbg_dump_sty(s, node->s2ty, node->s2ba, "S2TY");
bdisp_dbg_dump_xy(s, node->s2xy, "S2XY");
bdisp_dbg_dump_sz(s, node->s2sz, "S2SZ");
seq_puts(s, "-- Source 3 --\n");
seq_printf(s, "S3BA\t0x%08X\n", node->s3ba);
bdisp_dbg_dump_sty(s, node->s3ty, node->s3ba, "S3TY");
bdisp_dbg_dump_xy(s, node->s3xy, "S3XY");
bdisp_dbg_dump_sz(s, node->s3sz, "S3SZ");
/* Clipping not dumped */
/* CLUT not dumped */
seq_puts(s, "-- Filter & Mask --\n");
bdisp_dbg_dump_fctl(s, node->fctl);
/* PMK not dumped */
seq_puts(s, "-- Chroma Filter --\n");
bdisp_dbg_dump_rsf(s, node->rsf, "RSF");
bdisp_dbg_dump_rzi(s, node->rzi, "RZI");
seq_printf(s, "HFP\t0x%08X\n", node->hfp);
seq_printf(s, "VFP\t0x%08X\n", node->vfp);
seq_puts(s, "-- Luma Filter --\n");
bdisp_dbg_dump_rsf(s, node->y_rsf, "Y_RSF");
bdisp_dbg_dump_rzi(s, node->y_rzi, "Y_RZI");
seq_printf(s, "Y_HFP\t0x%08X\n", node->y_hfp);
seq_printf(s, "Y_VFP\t0x%08X\n", node->y_vfp);
/* Flicker not dumped */
/* Color key not dumped */
/* Reserved not dumped */
/* Static Address & User not dumped */
seq_puts(s, "-- Input Versatile Matrix --\n");
bdisp_dbg_dump_ivmx(s, node->ivmx0, node->ivmx1,
node->ivmx2, node->ivmx3);
/* Output Versatile Matrix not dumped */
/* Pace not dumped */
/* VC1R & DEI not dumped */
/* Gradient Fill not dumped */
} while ((++i < MAX_NB_NODE) && node->nip);
return 0;
}
static int last_nodes_raw_show(struct seq_file *s, void *data)
{
struct bdisp_dev *bdisp = s->private;
struct bdisp_node *node;
u32 *val;
int j, i = 0;
if (!bdisp->dbg.copy_node[0]) {
seq_puts(s, "No node built yet\n");
return 0;
}
do {
node = bdisp->dbg.copy_node[i];
if (!node)
break;
seq_printf(s, "--------\nNode %d:\n", i);
val = (u32 *)node;
for (j = 0; j < sizeof(struct bdisp_node) / sizeof(u32); j++)
seq_printf(s, "0x%08X\n", *val++);
} while ((++i < MAX_NB_NODE) && node->nip);
return 0;
}
static const char *bdisp_fmt_to_str(struct bdisp_frame frame)
{
switch (frame.fmt->pixelformat) {
case V4L2_PIX_FMT_YUV420:
return "YUV420P";
case V4L2_PIX_FMT_NV12:
if (frame.field == V4L2_FIELD_INTERLACED)
return "NV12 interlaced";
else
return "NV12";
case V4L2_PIX_FMT_RGB565:
return "RGB16";
case V4L2_PIX_FMT_RGB24:
return "RGB24";
case V4L2_PIX_FMT_XBGR32:
return "XRGB";
case V4L2_PIX_FMT_ABGR32:
return "ARGB";
default:
return "????";
}
}
static int last_request_show(struct seq_file *s, void *data)
{
struct bdisp_dev *bdisp = s->private;
struct bdisp_request *request = &bdisp->dbg.copy_request;
struct bdisp_frame src, dst;
if (!request->nb_req) {
seq_puts(s, "No request\n");
return 0;
}
src = request->src;
dst = request->dst;
seq_printf(s, "\nRequest #%d\n", request->nb_req);
seq_printf(s, "Format: %s\t\t\t%s\n",
bdisp_fmt_to_str(src), bdisp_fmt_to_str(dst));
seq_printf(s, "Crop area: (%d,%d)/%ux%u ==>\t(%d,%d)/%ux%u\n",
src.crop.left, src.crop.top,
src.crop.width, src.crop.height,
dst.crop.left, dst.crop.top,
dst.crop.width, dst.crop.height);
seq_printf(s, "Buff size: %dx%d\t\t%dx%d\n\n",
src.width, src.height, dst.width, dst.height);
if (request->hflip)
seq_puts(s, "Horizontal flip\n\n");
if (request->vflip)
seq_puts(s, "Vertical flip\n\n");
return 0;
}
#define DUMP(reg) seq_printf(s, #reg " \t0x%08X\n", readl(bdisp->regs + reg))
static int regs_show(struct seq_file *s, void *data)
{
struct bdisp_dev *bdisp = s->private;
int ret;
unsigned int i;
ret = pm_runtime_resume_and_get(bdisp->dev);
if (ret < 0) {
seq_puts(s, "Cannot wake up IP\n");
return 0;
}
seq_printf(s, "Reg @ = 0x%p\n", bdisp->regs);
seq_puts(s, "\nStatic:\n");
DUMP(BLT_CTL);
DUMP(BLT_ITS);
DUMP(BLT_STA1);
DUMP(BLT_AQ1_CTL);
DUMP(BLT_AQ1_IP);
DUMP(BLT_AQ1_LNA);
DUMP(BLT_AQ1_STA);
DUMP(BLT_ITM0);
seq_puts(s, "\nPlugs:\n");
DUMP(BLT_PLUGS1_OP2);
DUMP(BLT_PLUGS1_CHZ);
DUMP(BLT_PLUGS1_MSZ);
DUMP(BLT_PLUGS1_PGZ);
DUMP(BLT_PLUGS2_OP2);
DUMP(BLT_PLUGS2_CHZ);
DUMP(BLT_PLUGS2_MSZ);
DUMP(BLT_PLUGS2_PGZ);
DUMP(BLT_PLUGS3_OP2);
DUMP(BLT_PLUGS3_CHZ);
DUMP(BLT_PLUGS3_MSZ);
DUMP(BLT_PLUGS3_PGZ);
DUMP(BLT_PLUGT_OP2);
DUMP(BLT_PLUGT_CHZ);
DUMP(BLT_PLUGT_MSZ);
DUMP(BLT_PLUGT_PGZ);
seq_puts(s, "\nNode:\n");
DUMP(BLT_NIP);
DUMP(BLT_CIC);
DUMP(BLT_INS);
DUMP(BLT_ACK);
DUMP(BLT_TBA);
DUMP(BLT_TTY);
DUMP(BLT_TXY);
DUMP(BLT_TSZ);
DUMP(BLT_S1BA);
DUMP(BLT_S1TY);
DUMP(BLT_S1XY);
DUMP(BLT_S2BA);
DUMP(BLT_S2TY);
DUMP(BLT_S2XY);
DUMP(BLT_S2SZ);
DUMP(BLT_S3BA);
DUMP(BLT_S3TY);
DUMP(BLT_S3XY);
DUMP(BLT_S3SZ);
DUMP(BLT_FCTL);
DUMP(BLT_RSF);
DUMP(BLT_RZI);
DUMP(BLT_HFP);
DUMP(BLT_VFP);
DUMP(BLT_Y_RSF);
DUMP(BLT_Y_RZI);
DUMP(BLT_Y_HFP);
DUMP(BLT_Y_VFP);
DUMP(BLT_IVMX0);
DUMP(BLT_IVMX1);
DUMP(BLT_IVMX2);
DUMP(BLT_IVMX3);
DUMP(BLT_OVMX0);
DUMP(BLT_OVMX1);
DUMP(BLT_OVMX2);
DUMP(BLT_OVMX3);
DUMP(BLT_DEI);
seq_puts(s, "\nFilter:\n");
for (i = 0; i < BLT_NB_H_COEF; i++) {
seq_printf(s, "BLT_HFC%d \t0x%08X\n", i,
readl(bdisp->regs + BLT_HFC_N + i * 4));
}
for (i = 0; i < BLT_NB_V_COEF; i++) {
seq_printf(s, "BLT_VFC%d \t0x%08X\n", i,
readl(bdisp->regs + BLT_VFC_N + i * 4));
}
seq_puts(s, "\nLuma filter:\n");
for (i = 0; i < BLT_NB_H_COEF; i++) {
seq_printf(s, "BLT_Y_HFC%d \t0x%08X\n", i,
readl(bdisp->regs + BLT_Y_HFC_N + i * 4));
}
for (i = 0; i < BLT_NB_V_COEF; i++) {
seq_printf(s, "BLT_Y_VFC%d \t0x%08X\n", i,
readl(bdisp->regs + BLT_Y_VFC_N + i * 4));
}
pm_runtime_put(bdisp->dev);
return 0;
}
#define SECOND 1000000
static int perf_show(struct seq_file *s, void *data)
{
struct bdisp_dev *bdisp = s->private;
struct bdisp_request *request = &bdisp->dbg.copy_request;
s64 avg_time_us;
int avg_fps, min_fps, max_fps, last_fps;
if (!request->nb_req) {
seq_puts(s, "No request\n");
return 0;
}
avg_time_us = div64_s64(bdisp->dbg.tot_duration, request->nb_req);
if (avg_time_us > SECOND)
avg_fps = 0;
else
avg_fps = SECOND / (s32)avg_time_us;
if (bdisp->dbg.min_duration > SECOND)
min_fps = 0;
else
min_fps = SECOND / (s32)bdisp->dbg.min_duration;
if (bdisp->dbg.max_duration > SECOND)
max_fps = 0;
else
max_fps = SECOND / (s32)bdisp->dbg.max_duration;
if (bdisp->dbg.last_duration > SECOND)
last_fps = 0;
else
last_fps = SECOND / (s32)bdisp->dbg.last_duration;
seq_printf(s, "HW processing (%d requests):\n", request->nb_req);
seq_printf(s, " Average: %5lld us (%3d fps)\n",
avg_time_us, avg_fps);
seq_printf(s, " Min-Max: %5lld us (%3d fps) - %5lld us (%3d fps)\n",
bdisp->dbg.min_duration, min_fps,
bdisp->dbg.max_duration, max_fps);
seq_printf(s, " Last: %5lld us (%3d fps)\n",
bdisp->dbg.last_duration, last_fps);
return 0;
}
#define bdisp_dbg_create_entry(name) \
debugfs_create_file(#name, S_IRUGO, bdisp->dbg.debugfs_entry, bdisp, \
&name##_fops)
DEFINE_SHOW_ATTRIBUTE(regs);
DEFINE_SHOW_ATTRIBUTE(last_nodes);
DEFINE_SHOW_ATTRIBUTE(last_nodes_raw);
DEFINE_SHOW_ATTRIBUTE(last_request);
DEFINE_SHOW_ATTRIBUTE(perf);
void bdisp_debugfs_create(struct bdisp_dev *bdisp)
{
char dirname[16];
snprintf(dirname, sizeof(dirname), "%s%d", BDISP_NAME, bdisp->id);
bdisp->dbg.debugfs_entry = debugfs_create_dir(dirname, NULL);
bdisp_dbg_create_entry(regs);
bdisp_dbg_create_entry(last_nodes);
bdisp_dbg_create_entry(last_nodes_raw);
bdisp_dbg_create_entry(last_request);
bdisp_dbg_create_entry(perf);
}
void bdisp_debugfs_remove(struct bdisp_dev *bdisp)
{
debugfs_remove_recursive(bdisp->dbg.debugfs_entry);
bdisp->dbg.debugfs_entry = NULL;
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#define BDISP_HF_NB 64
#define BDISP_VF_NB 40
/**
* struct bdisp_filter_h_spec - Horizontal filter specification
*
* @min: min scale factor for this filter (6.10 fixed point)
* @max: max scale factor for this filter (6.10 fixed point)
* @coef: filter coefficients
*/
struct bdisp_filter_h_spec {
const u16 min;
const u16 max;
const u8 coef[BDISP_HF_NB];
};
/**
* struct bdisp_filter_v_spec - Vertical filter specification
*
* @min: min scale factor for this filter (6.10 fixed point)
* @max: max scale factor for this filter (6.10 fixed point)
* @coef: filter coefficients
*/
struct bdisp_filter_v_spec {
const u16 min;
const u16 max;
const u8 coef[BDISP_VF_NB];
};
/* RGB YUV 601 standard conversion */
static const u32 bdisp_rgb_to_yuv[] = {
0x0e1e8bee, 0x08420419, 0xfb5ed471, 0x08004080,
};
static const u32 bdisp_yuv_to_rgb[] = {
0x3324a800, 0xe604ab9c, 0x0004a957, 0x32121eeb,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,235 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
struct bdisp_node {
/* 0 - General */
u32 nip;
u32 cic;
u32 ins;
u32 ack;
/* 1 - Target */
u32 tba;
u32 tty;
u32 txy;
u32 tsz;
/* 2 - Color Fill */
u32 s1cf;
u32 s2cf;
/* 3 - Source 1 */
u32 s1ba;
u32 s1ty;
u32 s1xy;
u32 s1sz_tsz;
/* 4 - Source 2 */
u32 s2ba;
u32 s2ty;
u32 s2xy;
u32 s2sz;
/* 5 - Source 3 */
u32 s3ba;
u32 s3ty;
u32 s3xy;
u32 s3sz;
/* 6 - Clipping */
u32 cwo;
u32 cws;
/* 7 - CLUT */
u32 cco;
u32 cml;
/* 8 - Filter & Mask */
u32 fctl;
u32 pmk;
/* 9 - Chroma Filter */
u32 rsf;
u32 rzi;
u32 hfp;
u32 vfp;
/* 10 - Luma Filter */
u32 y_rsf;
u32 y_rzi;
u32 y_hfp;
u32 y_vfp;
/* 11 - Flicker */
u32 ff0;
u32 ff1;
u32 ff2;
u32 ff3;
/* 12 - Color Key */
u32 key1;
u32 key2;
/* 14 - Static Address & User */
u32 sar;
u32 usr;
/* 15 - Input Versatile Matrix */
u32 ivmx0;
u32 ivmx1;
u32 ivmx2;
u32 ivmx3;
/* 16 - Output Versatile Matrix */
u32 ovmx0;
u32 ovmx1;
u32 ovmx2;
u32 ovmx3;
/* 17 - Pace */
u32 pace;
/* 18 - VC1R & DEI */
u32 vc1r;
u32 dei;
/* 19 - Gradient Fill */
u32 hgf;
u32 vgf;
};
/* HW registers : static */
#define BLT_CTL 0x0A00
#define BLT_ITS 0x0A04
#define BLT_STA1 0x0A08
#define BLT_AQ1_CTL 0x0A60
#define BLT_AQ1_IP 0x0A64
#define BLT_AQ1_LNA 0x0A68
#define BLT_AQ1_STA 0x0A6C
#define BLT_ITM0 0x0AD0
/* HW registers : plugs */
#define BLT_PLUGS1_OP2 0x0B04
#define BLT_PLUGS1_CHZ 0x0B08
#define BLT_PLUGS1_MSZ 0x0B0C
#define BLT_PLUGS1_PGZ 0x0B10
#define BLT_PLUGS2_OP2 0x0B24
#define BLT_PLUGS2_CHZ 0x0B28
#define BLT_PLUGS2_MSZ 0x0B2C
#define BLT_PLUGS2_PGZ 0x0B30
#define BLT_PLUGS3_OP2 0x0B44
#define BLT_PLUGS3_CHZ 0x0B48
#define BLT_PLUGS3_MSZ 0x0B4C
#define BLT_PLUGS3_PGZ 0x0B50
#define BLT_PLUGT_OP2 0x0B84
#define BLT_PLUGT_CHZ 0x0B88
#define BLT_PLUGT_MSZ 0x0B8C
#define BLT_PLUGT_PGZ 0x0B90
/* HW registers : node */
#define BLT_NIP 0x0C00
#define BLT_CIC 0x0C04
#define BLT_INS 0x0C08
#define BLT_ACK 0x0C0C
#define BLT_TBA 0x0C10
#define BLT_TTY 0x0C14
#define BLT_TXY 0x0C18
#define BLT_TSZ 0x0C1C
#define BLT_S1BA 0x0C28
#define BLT_S1TY 0x0C2C
#define BLT_S1XY 0x0C30
#define BLT_S2BA 0x0C38
#define BLT_S2TY 0x0C3C
#define BLT_S2XY 0x0C40
#define BLT_S2SZ 0x0C44
#define BLT_S3BA 0x0C48
#define BLT_S3TY 0x0C4C
#define BLT_S3XY 0x0C50
#define BLT_S3SZ 0x0C54
#define BLT_FCTL 0x0C68
#define BLT_RSF 0x0C70
#define BLT_RZI 0x0C74
#define BLT_HFP 0x0C78
#define BLT_VFP 0x0C7C
#define BLT_Y_RSF 0x0C80
#define BLT_Y_RZI 0x0C84
#define BLT_Y_HFP 0x0C88
#define BLT_Y_VFP 0x0C8C
#define BLT_IVMX0 0x0CC0
#define BLT_IVMX1 0x0CC4
#define BLT_IVMX2 0x0CC8
#define BLT_IVMX3 0x0CCC
#define BLT_OVMX0 0x0CD0
#define BLT_OVMX1 0x0CD4
#define BLT_OVMX2 0x0CD8
#define BLT_OVMX3 0x0CDC
#define BLT_DEI 0x0CEC
/* HW registers : filters */
#define BLT_HFC_N 0x0D00
#define BLT_VFC_N 0x0D90
#define BLT_Y_HFC_N 0x0E00
#define BLT_Y_VFC_N 0x0E90
#define BLT_NB_H_COEF 16
#define BLT_NB_V_COEF 10
/* Registers values */
#define BLT_CTL_RESET BIT(31) /* Global soft reset */
#define BLT_ITS_AQ1_LNA BIT(12) /* AQ1 LNA reached */
#define BLT_STA1_IDLE BIT(0) /* BDISP idle */
#define BLT_AQ1_CTL_CFG 0x80400003 /* Enable, P3, LNA reached */
#define BLT_INS_S1_MASK (BIT(0) | BIT(1) | BIT(2))
#define BLT_INS_S1_OFF 0x00000000 /* src1 disabled */
#define BLT_INS_S1_MEM 0x00000001 /* src1 fetched from memory */
#define BLT_INS_S1_CF 0x00000003 /* src1 color fill */
#define BLT_INS_S1_COPY 0x00000004 /* src1 direct copy */
#define BLT_INS_S1_FILL 0x00000007 /* src1 firect fill */
#define BLT_INS_S2_MASK (BIT(3) | BIT(4))
#define BLT_INS_S2_OFF 0x00000000 /* src2 disabled */
#define BLT_INS_S2_MEM 0x00000008 /* src2 fetched from memory */
#define BLT_INS_S2_CF 0x00000018 /* src2 color fill */
#define BLT_INS_S3_MASK BIT(5)
#define BLT_INS_S3_OFF 0x00000000 /* src3 disabled */
#define BLT_INS_S3_MEM 0x00000020 /* src3 fetched from memory */
#define BLT_INS_IVMX BIT(6) /* Input versatile matrix */
#define BLT_INS_CLUT BIT(7) /* Color Look Up Table */
#define BLT_INS_SCALE BIT(8) /* Scaling */
#define BLT_INS_FLICK BIT(9) /* Flicker filter */
#define BLT_INS_CLIP BIT(10) /* Clipping */
#define BLT_INS_CKEY BIT(11) /* Color key */
#define BLT_INS_OVMX BIT(12) /* Output versatile matrix */
#define BLT_INS_DEI BIT(13) /* Deinterlace */
#define BLT_INS_PMASK BIT(14) /* Plane mask */
#define BLT_INS_VC1R BIT(17) /* VC1 Range mapping */
#define BLT_INS_ROTATE BIT(18) /* Rotation */
#define BLT_INS_GRAD BIT(19) /* Gradient fill */
#define BLT_INS_AQLOCK BIT(29) /* AQ lock */
#define BLT_INS_PACE BIT(30) /* Pace down */
#define BLT_INS_IRQ BIT(31) /* Raise IRQ when node done */
#define BLT_CIC_ALL_GRP 0x000FDFFC /* all valid groups present */
#define BLT_ACK_BYPASS_S2S3 0x00000007 /* Bypass src2 and src3 */
#define BLT_TTY_COL_SHIFT 16 /* Color format */
#define BLT_TTY_COL_MASK 0x001F0000 /* Color format mask */
#define BLT_TTY_ALPHA_R BIT(21) /* Alpha range */
#define BLT_TTY_CR_NOT_CB BIT(22) /* CR not Cb */
#define BLT_TTY_MB BIT(23) /* MB frame / field*/
#define BLT_TTY_HSO BIT(24) /* H scan order */
#define BLT_TTY_VSO BIT(25) /* V scan order */
#define BLT_TTY_DITHER BIT(26) /* Dithering */
#define BLT_TTY_CHROMA BIT(27) /* Write chroma / luma */
#define BLT_TTY_BIG_END BIT(30) /* Big endianness */
#define BLT_S1TY_A1_SUBSET BIT(22) /* A1 subset */
#define BLT_S1TY_CHROMA_EXT BIT(26) /* Chroma Extended */
#define BTL_S1TY_SUBBYTE BIT(28) /* Sub-byte fmt, pixel order */
#define BLT_S1TY_RGB_EXP BIT(29) /* RGB expansion mode */
#define BLT_S2TY_A1_SUBSET BIT(22) /* A1 subset */
#define BLT_S2TY_CHROMA_EXT BIT(26) /* Chroma Extended */
#define BTL_S2TY_SUBBYTE BIT(28) /* Sub-byte fmt, pixel order */
#define BLT_S2TY_RGB_EXP BIT(29) /* RGB expansion mode */
#define BLT_S3TY_BLANK_ACC BIT(26) /* Blank access */
#define BLT_FCTL_HV_SCALE 0x00000055 /* H/V resize + color filter */
#define BLT_FCTL_Y_HV_SCALE 0x33000000 /* Luma version */
#define BLT_FCTL_HV_SAMPLE 0x00000044 /* H/V resize */
#define BLT_FCTL_Y_HV_SAMPLE 0x22000000 /* Luma version */
#define BLT_RZI_DEFAULT 0x20003000 /* H/VNB_repeat = 3/2 */
/* Color format */
#define BDISP_RGB565 0x00 /* RGB565 */
#define BDISP_RGB888 0x01 /* RGB888 */
#define BDISP_XRGB8888 0x02 /* RGB888_32 */
#define BDISP_ARGB8888 0x05 /* ARGB888 */
#define BDISP_NV12 0x16 /* YCbCr42x R2B */
#define BDISP_YUV_3B 0x1E /* YUV (3 buffer) */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2014
* Authors: Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/ktime.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-dma-contig.h>
#define BDISP_NAME "bdisp"
/*
* Max nb of nodes in node-list:
* - 2 nodes to handle wide 4K pictures
* - 2 nodes to handle two planes (Y & CbCr) */
#define MAX_OUTPUT_PLANES 2
#define MAX_VERTICAL_STRIDES 2
#define MAX_NB_NODE (MAX_OUTPUT_PLANES * MAX_VERTICAL_STRIDES)
/* struct bdisp_ctrls - bdisp control set
* @hflip: horizontal flip
* @vflip: vertical flip
*/
struct bdisp_ctrls {
struct v4l2_ctrl *hflip;
struct v4l2_ctrl *vflip;
};
/**
* struct bdisp_fmt - driver's internal color format data
* @pixelformat:fourcc code for this format
* @nb_planes: number of planes (ex: [0]=RGB/Y - [1]=Cb/Cr, ...)
* @bpp: bits per pixel (general)
* @bpp_plane0: byte per pixel for the 1st plane
* @w_align: width alignment in pixel (multiple of)
* @h_align: height alignment in pixel (multiple of)
*/
struct bdisp_fmt {
u32 pixelformat;
u8 nb_planes;
u8 bpp;
u8 bpp_plane0;
u8 w_align;
u8 h_align;
};
/**
* struct bdisp_frame - frame properties
*
* @width: frame width (including padding)
* @height: frame height (including padding)
* @fmt: pointer to frame format descriptor
* @field: frame / field type
* @bytesperline: stride of the 1st plane
* @sizeimage: image size in bytes
* @colorspace: colorspace
* @crop: crop area
* @paddr: image physical addresses per plane ([0]=RGB/Y - [1]=Cb/Cr, ...)
*/
struct bdisp_frame {
u32 width;
u32 height;
const struct bdisp_fmt *fmt;
enum v4l2_field field;
u32 bytesperline;
u32 sizeimage;
enum v4l2_colorspace colorspace;
struct v4l2_rect crop;
dma_addr_t paddr[4];
};
/**
* struct bdisp_request - bdisp request
*
* @src: source frame properties
* @dst: destination frame properties
* @hflip: horizontal flip
* @vflip: vertical flip
* @nb_req: number of run request
*/
struct bdisp_request {
struct bdisp_frame src;
struct bdisp_frame dst;
unsigned int hflip:1;
unsigned int vflip:1;
int nb_req;
};
/**
* struct bdisp_ctx - device context data
*
* @src: source frame properties
* @dst: destination frame properties
* @state: flags to keep track of user configuration
* @hflip: horizontal flip
* @vflip: vertical flip
* @bdisp_dev: the device this context applies to
* @node: node array
* @node_paddr: node physical address array
* @fh: v4l2 file handle
* @ctrl_handler: v4l2 controls handler
* @bdisp_ctrls: bdisp control set
* @ctrls_rdy: true if the control handler is initialized
*/
struct bdisp_ctx {
struct bdisp_frame src;
struct bdisp_frame dst;
u32 state;
unsigned int hflip:1;
unsigned int vflip:1;
struct bdisp_dev *bdisp_dev;
struct bdisp_node *node[MAX_NB_NODE];
dma_addr_t node_paddr[MAX_NB_NODE];
struct v4l2_fh fh;
struct v4l2_ctrl_handler ctrl_handler;
struct bdisp_ctrls bdisp_ctrls;
bool ctrls_rdy;
};
/**
* struct bdisp_m2m_device - v4l2 memory-to-memory device data
*
* @vdev: video device node for v4l2 m2m mode
* @m2m_dev: v4l2 m2m device data
* @ctx: hardware context data
* @refcnt: reference counter
*/
struct bdisp_m2m_device {
struct video_device *vdev;
struct v4l2_m2m_dev *m2m_dev;
struct bdisp_ctx *ctx;
int refcnt;
};
/**
* struct bdisp_dbg - debug info
*
* @debugfs_entry: debugfs
* @copy_node: array of last used nodes
* @copy_request: last bdisp request
* @hw_start: start time of last HW request
* @last_duration: last HW processing duration in microsecs
* @min_duration: min HW processing duration in microsecs
* @max_duration: max HW processing duration in microsecs
* @tot_duration: total HW processing duration in microsecs
*/
struct bdisp_dbg {
struct dentry *debugfs_entry;
struct bdisp_node *copy_node[MAX_NB_NODE];
struct bdisp_request copy_request;
ktime_t hw_start;
s64 last_duration;
s64 min_duration;
s64 max_duration;
s64 tot_duration;
};
/**
* struct bdisp_dev - abstraction for bdisp entity
*
* @v4l2_dev: v4l2 device
* @vdev: video device
* @pdev: platform device
* @dev: device
* @lock: mutex protecting this data structure
* @slock: spinlock protecting this data structure
* @id: device index
* @m2m: memory-to-memory V4L2 device information
* @state: flags used to synchronize m2m and capture mode operation
* @clock: IP clock
* @regs: registers
* @irq_queue: interrupt handler waitqueue
* @work_queue: workqueue to handle timeouts
* @timeout_work: IRQ timeout structure
* @dbg: debug info
*/
struct bdisp_dev {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct platform_device *pdev;
struct device *dev;
spinlock_t slock;
struct mutex lock;
u16 id;
struct bdisp_m2m_device m2m;
unsigned long state;
struct clk *clock;
void __iomem *regs;
wait_queue_head_t irq_queue;
struct workqueue_struct *work_queue;
struct delayed_work timeout_work;
struct bdisp_dbg dbg;
};
void bdisp_hw_free_nodes(struct bdisp_ctx *ctx);
int bdisp_hw_alloc_nodes(struct bdisp_ctx *ctx);
void bdisp_hw_free_filters(struct device *dev);
int bdisp_hw_alloc_filters(struct device *dev);
int bdisp_hw_reset(struct bdisp_dev *bdisp);
int bdisp_hw_get_and_clear_irq(struct bdisp_dev *bdisp);
int bdisp_hw_update(struct bdisp_ctx *ctx);
void bdisp_debugfs_remove(struct bdisp_dev *bdisp);
void bdisp_debugfs_create(struct bdisp_dev *bdisp);
void bdisp_dbg_perf_begin(struct bdisp_dev *bdisp);
void bdisp_dbg_perf_end(struct bdisp_dev *bdisp);
@@ -0,0 +1,36 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_STI_DELTA
tristate "STMicroelectronics DELTA multi-format video decoder V4L2 driver"
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_STI || COMPILE_TEST
help
This V4L2 driver enables DELTA multi-format video decoder
of STMicroelectronics STiH4xx SoC series allowing hardware
decoding of various compressed video bitstream format in
raw uncompressed format.
Use this option to see the decoders available for such
hardware.
Please notice that the driver will only be built if
at least one of the DELTA decoder below is selected.
config VIDEO_STI_DELTA_MJPEG
bool "STMicroelectronics DELTA MJPEG support"
default y
depends on VIDEO_STI_DELTA
help
Enables DELTA MJPEG hardware support.
To compile this driver as a module, choose M here:
the module will be called st-delta.
config VIDEO_STI_DELTA_DRIVER
tristate
depends on VIDEO_STI_DELTA
depends on VIDEO_STI_DELTA_MJPEG
default VIDEO_STI_DELTA_MJPEG
select VIDEOBUF2_DMA_CONTIG
select V4L2_MEM2MEM_DEV
select RPMSG
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO_STI_DELTA_DRIVER) += st-delta.o
st-delta-y := delta-v4l2.o delta-mem.o delta-ipc.o delta-debug.o
# MJPEG support
st-delta-$(CONFIG_VIDEO_STI_DELTA_MJPEG) += delta-mjpeg-hdr.o
st-delta-$(CONFIG_VIDEO_STI_DELTA_MJPEG) += delta-mjpeg-dec.o
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_CFG_H
#define DELTA_CFG_H
#define DELTA_FW_VERSION "21.1-3"
#define DELTA_MIN_WIDTH 32
#define DELTA_MAX_WIDTH 4096
#define DELTA_MIN_HEIGHT 32
#define DELTA_MAX_HEIGHT 2400
/* DELTA requires a 32x32 pixels alignment for frames */
#define DELTA_WIDTH_ALIGNMENT 32
#define DELTA_HEIGHT_ALIGNMENT 32
#define DELTA_DEFAULT_WIDTH DELTA_MIN_WIDTH
#define DELTA_DEFAULT_HEIGHT DELTA_MIN_HEIGHT
#define DELTA_DEFAULT_FRAMEFORMAT V4L2_PIX_FMT_NV12
#define DELTA_DEFAULT_STREAMFORMAT V4L2_PIX_FMT_MJPEG
#define DELTA_MAX_RESO (DELTA_MAX_WIDTH * DELTA_MAX_HEIGHT)
/* guard value for number of access units */
#define DELTA_MAX_AUS 10
/* IP perf dependent, can be tuned */
#define DELTA_PEAK_FRAME_SMOOTHING 2
/*
* guard output frame count:
* - at least 1 frame needed for display
* - at worst 21
* ( max h264 dpb (16) +
* decoding peak smoothing (2) +
* user display pipeline (3) )
*/
#define DELTA_MIN_FRAME_USER 1
#define DELTA_MAX_DPB 16
#define DELTA_MAX_FRAME_USER 3 /* platform/use-case dependent */
#define DELTA_MAX_FRAMES (DELTA_MAX_DPB + DELTA_PEAK_FRAME_SMOOTHING +\
DELTA_MAX_FRAME_USER)
#if DELTA_MAX_FRAMES > VIDEO_MAX_FRAME
#undef DELTA_MAX_FRAMES
#define DELTA_MAX_FRAMES (VIDEO_MAX_FRAME)
#endif
/* extra space to be allocated to store codec specific data per frame */
#define DELTA_MAX_FRAME_PRIV_SIZE 100
/* PM runtime auto power-off after 5ms of inactivity */
#define DELTA_HW_AUTOSUSPEND_DELAY_MS 5
#define DELTA_MAX_DECODERS 10
#ifdef CONFIG_VIDEO_STI_DELTA_MJPEG
extern const struct delta_dec mjpegdec;
#endif
#endif /* DELTA_CFG_H */
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Hugues Fruchet <hugues.fruchet@st.com>
* Fabrice Lecoultre <fabrice.lecoultre@st.com>
* for STMicroelectronics.
*/
#include "delta.h"
#include "delta-debug.h"
char *delta_streaminfo_str(struct delta_streaminfo *s, char *str,
unsigned int len)
{
if (!s)
return NULL;
snprintf(str, len,
"%4.4s %dx%d %s %s dpb=%d %s %s %s(%d,%d)/%ux%u %s%d/%d",
(char *)&s->streamformat, s->width, s->height,
s->profile, s->level, s->dpb,
(s->field == V4L2_FIELD_NONE) ? "progressive" : "interlaced",
s->other,
s->flags & DELTA_STREAMINFO_FLAG_CROP ? "crop=" : "",
s->crop.left, s->crop.top,
s->crop.width, s->crop.height,
s->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT ? "par=" : "",
s->pixelaspect.numerator,
s->pixelaspect.denominator);
return str;
}
char *delta_frameinfo_str(struct delta_frameinfo *f, char *str,
unsigned int len)
{
if (!f)
return NULL;
snprintf(str, len,
"%4.4s %dx%d aligned %dx%d %s %s(%d,%d)/%ux%u %s%d/%d",
(char *)&f->pixelformat, f->width, f->height,
f->aligned_width, f->aligned_height,
(f->field == V4L2_FIELD_NONE) ? "progressive" : "interlaced",
f->flags & DELTA_STREAMINFO_FLAG_CROP ? "crop=" : "",
f->crop.left, f->crop.top,
f->crop.width, f->crop.height,
f->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT ? "par=" : "",
f->pixelaspect.numerator,
f->pixelaspect.denominator);
return str;
}
void delta_trace_summary(struct delta_ctx *ctx)
{
struct delta_dev *delta = ctx->dev;
struct delta_streaminfo *s = &ctx->streaminfo;
unsigned char str[100] = "";
if (!(ctx->flags & DELTA_FLAG_STREAMINFO))
return;
dev_dbg(delta->dev, "%s %s, %d frames decoded, %d frames output, %d frames dropped, %d stream errors, %d decode errors",
ctx->name,
delta_streaminfo_str(s, str, sizeof(str)),
ctx->decoded_frames,
ctx->output_frames,
ctx->dropped_frames,
ctx->stream_errors,
ctx->decode_errors);
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Hugues Fruchet <hugues.fruchet@st.com>
* Fabrice Lecoultre <fabrice.lecoultre@st.com>
* for STMicroelectronics.
*/
#ifndef DELTA_DEBUG_H
#define DELTA_DEBUG_H
char *delta_streaminfo_str(struct delta_streaminfo *s, char *str,
unsigned int len);
char *delta_frameinfo_str(struct delta_frameinfo *f, char *str,
unsigned int len);
void delta_trace_summary(struct delta_ctx *ctx);
#endif /* DELTA_DEBUG_H */
@@ -0,0 +1,591 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#include <linux/rpmsg.h>
#include "delta.h"
#include "delta-ipc.h"
#include "delta-mem.h"
#define IPC_TIMEOUT 100
#define IPC_SANITY_TAG 0xDEADBEEF
enum delta_ipc_fw_command {
DELTA_IPC_OPEN,
DELTA_IPC_SET_STREAM,
DELTA_IPC_DECODE,
DELTA_IPC_CLOSE
};
#define to_rpmsg_driver(__drv) container_of(__drv, struct rpmsg_driver, drv)
#define to_delta(__d) container_of(__d, struct delta_dev, rpmsg_driver)
#define to_ctx(hdl) ((struct delta_ipc_ctx *)hdl)
#define to_pctx(ctx) container_of(ctx, struct delta_ctx, ipc_ctx)
struct delta_ipc_header_msg {
u32 tag;
void *host_hdl;
u32 copro_hdl;
u32 command;
};
#define to_host_hdl(ctx) ((void *)ctx)
#define msg_to_ctx(msg) ((struct delta_ipc_ctx *)(msg)->header.host_hdl)
#define msg_to_copro_hdl(msg) ((msg)->header.copro_hdl)
static inline dma_addr_t to_paddr(struct delta_ipc_ctx *ctx, void *vaddr)
{
return (ctx->ipc_buf->paddr + (vaddr - ctx->ipc_buf->vaddr));
}
static inline bool is_valid_data(struct delta_ipc_ctx *ctx,
void *data, u32 size)
{
return ((data >= ctx->ipc_buf->vaddr) &&
((data + size) <= (ctx->ipc_buf->vaddr + ctx->ipc_buf->size)));
}
/*
* IPC shared memory (@ipc_buf_size, @ipc_buf_paddr) is sent to copro
* at each instance opening. This memory is allocated by IPC client
* and given through delta_ipc_open(). All messages parameters
* (open, set_stream, decode) will have their phy address within
* this IPC shared memory, avoiding de-facto recopies inside delta-ipc.
* All the below messages structures are used on both host and firmware
* side and are packed (use only of 32 bits size fields in messages
* structures to ensure packing):
* - struct delta_ipc_open_msg
* - struct delta_ipc_set_stream_msg
* - struct delta_ipc_decode_msg
* - struct delta_ipc_close_msg
* - struct delta_ipc_cb_msg
*/
struct delta_ipc_open_msg {
struct delta_ipc_header_msg header;
u32 ipc_buf_size;
dma_addr_t ipc_buf_paddr;
char name[32];
u32 param_size;
dma_addr_t param_paddr;
};
struct delta_ipc_set_stream_msg {
struct delta_ipc_header_msg header;
u32 param_size;
dma_addr_t param_paddr;
};
struct delta_ipc_decode_msg {
struct delta_ipc_header_msg header;
u32 param_size;
dma_addr_t param_paddr;
u32 status_size;
dma_addr_t status_paddr;
};
struct delta_ipc_close_msg {
struct delta_ipc_header_msg header;
};
struct delta_ipc_cb_msg {
struct delta_ipc_header_msg header;
int err;
};
static void build_msg_header(struct delta_ipc_ctx *ctx,
enum delta_ipc_fw_command command,
struct delta_ipc_header_msg *header)
{
header->tag = IPC_SANITY_TAG;
header->host_hdl = to_host_hdl(ctx);
header->copro_hdl = ctx->copro_hdl;
header->command = command;
}
int delta_ipc_open(struct delta_ctx *pctx, const char *name,
struct delta_ipc_param *param, u32 ipc_buf_size,
struct delta_buf **ipc_buf, void **hdl)
{
struct delta_dev *delta = pctx->dev;
struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
struct delta_ipc_ctx *ctx = &pctx->ipc_ctx;
struct delta_ipc_open_msg msg;
struct delta_buf *buf = &ctx->ipc_buf_struct;
int ret;
if (!rpmsg_device) {
dev_err(delta->dev,
"%s ipc: failed to open, rpmsg is not initialized\n",
pctx->name);
pctx->sys_errors++;
return -EINVAL;
}
if (!name) {
dev_err(delta->dev,
"%s ipc: failed to open, no name given\n",
pctx->name);
return -EINVAL;
}
if (!param || !param->data || !param->size) {
dev_err(delta->dev,
"%s ipc: failed to open, empty parameter\n",
pctx->name);
return -EINVAL;
}
if (!ipc_buf_size) {
dev_err(delta->dev,
"%s ipc: failed to open, no size given for ipc buffer\n",
pctx->name);
return -EINVAL;
}
if (param->size > ipc_buf_size) {
dev_err(delta->dev,
"%s ipc: failed to open, too large ipc parameter (%d bytes while max %d expected)\n",
pctx->name,
param->size, ctx->ipc_buf->size);
return -EINVAL;
}
/* init */
init_completion(&ctx->done);
/*
* allocation of contiguous buffer for
* data of commands exchanged between
* host and firmware coprocessor
*/
ret = hw_alloc(pctx, ipc_buf_size,
"ipc data buffer", buf);
if (ret)
return ret;
ctx->ipc_buf = buf;
/* build rpmsg message */
build_msg_header(ctx, DELTA_IPC_OPEN, &msg.header);
msg.ipc_buf_size = ipc_buf_size;
msg.ipc_buf_paddr = ctx->ipc_buf->paddr;
strscpy(msg.name, name, sizeof(msg.name));
msg.param_size = param->size;
memcpy(ctx->ipc_buf->vaddr, param->data, msg.param_size);
msg.param_paddr = ctx->ipc_buf->paddr;
/* send it */
ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
if (ret) {
dev_err(delta->dev,
"%s ipc: failed to open, rpmsg_send failed (%d) for DELTA_IPC_OPEN (name=%s, size=%d, data=%p)\n",
pctx->name,
ret, name, param->size, param->data);
goto err;
}
/* wait for acknowledge */
if (!wait_for_completion_timeout
(&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
dev_err(delta->dev,
"%s ipc: failed to open, timeout waiting for DELTA_IPC_OPEN callback (name=%s, size=%d, data=%p)\n",
pctx->name,
name, param->size, param->data);
ret = -ETIMEDOUT;
goto err;
}
/* command completed, check error */
if (ctx->cb_err) {
dev_err(delta->dev,
"%s ipc: failed to open, DELTA_IPC_OPEN completed but with error (%d) (name=%s, size=%d, data=%p)\n",
pctx->name,
ctx->cb_err, name, param->size, param->data);
ret = -EIO;
goto err;
}
*ipc_buf = ctx->ipc_buf;
*hdl = (void *)ctx;
return 0;
err:
pctx->sys_errors++;
hw_free(pctx, ctx->ipc_buf);
ctx->ipc_buf = NULL;
return ret;
};
int delta_ipc_set_stream(void *hdl, struct delta_ipc_param *param)
{
struct delta_ipc_ctx *ctx = to_ctx(hdl);
struct delta_ctx *pctx = to_pctx(ctx);
struct delta_dev *delta = pctx->dev;
struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
struct delta_ipc_set_stream_msg msg;
int ret;
if (!hdl) {
dev_err(delta->dev,
"%s ipc: failed to set stream, invalid ipc handle\n",
pctx->name);
return -EINVAL;
}
if (!rpmsg_device) {
dev_err(delta->dev,
"%s ipc: failed to set stream, rpmsg is not initialized\n",
pctx->name);
return -EINVAL;
}
if (!param || !param->data || !param->size) {
dev_err(delta->dev,
"%s ipc: failed to set stream, empty parameter\n",
pctx->name);
return -EINVAL;
}
if (param->size > ctx->ipc_buf->size) {
dev_err(delta->dev,
"%s ipc: failed to set stream, too large ipc parameter(%d bytes while max %d expected)\n",
pctx->name,
param->size, ctx->ipc_buf->size);
return -EINVAL;
}
if (!is_valid_data(ctx, param->data, param->size)) {
dev_err(delta->dev,
"%s ipc: failed to set stream, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n",
pctx->name,
param->size,
param->data,
ctx->ipc_buf->vaddr,
ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
return -EINVAL;
}
/* build rpmsg message */
build_msg_header(ctx, DELTA_IPC_SET_STREAM, &msg.header);
msg.param_size = param->size;
msg.param_paddr = to_paddr(ctx, param->data);
/* send it */
ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
if (ret) {
dev_err(delta->dev,
"%s ipc: failed to set stream, rpmsg_send failed (%d) for DELTA_IPC_SET_STREAM (size=%d, data=%p)\n",
pctx->name,
ret, param->size, param->data);
pctx->sys_errors++;
return ret;
}
/* wait for acknowledge */
if (!wait_for_completion_timeout
(&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
dev_err(delta->dev,
"%s ipc: failed to set stream, timeout waiting for DELTA_IPC_SET_STREAM callback (size=%d, data=%p)\n",
pctx->name,
param->size, param->data);
pctx->sys_errors++;
return -ETIMEDOUT;
}
/* command completed, check status */
if (ctx->cb_err) {
dev_err(delta->dev,
"%s ipc: failed to set stream, DELTA_IPC_SET_STREAM completed but with error (%d) (size=%d, data=%p)\n",
pctx->name,
ctx->cb_err, param->size, param->data);
pctx->sys_errors++;
return -EIO;
}
return 0;
}
int delta_ipc_decode(void *hdl, struct delta_ipc_param *param,
struct delta_ipc_param *status)
{
struct delta_ipc_ctx *ctx = to_ctx(hdl);
struct delta_ctx *pctx = to_pctx(ctx);
struct delta_dev *delta = pctx->dev;
struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
struct delta_ipc_decode_msg msg;
int ret;
if (!hdl) {
dev_err(delta->dev,
"%s ipc: failed to decode, invalid ipc handle\n",
pctx->name);
return -EINVAL;
}
if (!rpmsg_device) {
dev_err(delta->dev,
"%s ipc: failed to decode, rpmsg is not initialized\n",
pctx->name);
return -EINVAL;
}
if (!param || !param->data || !param->size) {
dev_err(delta->dev,
"%s ipc: failed to decode, empty parameter\n",
pctx->name);
return -EINVAL;
}
if (!status || !status->data || !status->size) {
dev_err(delta->dev,
"%s ipc: failed to decode, empty status\n",
pctx->name);
return -EINVAL;
}
if (param->size + status->size > ctx->ipc_buf->size) {
dev_err(delta->dev,
"%s ipc: failed to decode, too large ipc parameter (%d bytes (param) + %d bytes (status) while max %d expected)\n",
pctx->name,
param->size,
status->size,
ctx->ipc_buf->size);
return -EINVAL;
}
if (!is_valid_data(ctx, param->data, param->size)) {
dev_err(delta->dev,
"%s ipc: failed to decode, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n",
pctx->name,
param->size,
param->data,
ctx->ipc_buf->vaddr,
ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
return -EINVAL;
}
if (!is_valid_data(ctx, status->data, status->size)) {
dev_err(delta->dev,
"%s ipc: failed to decode, status is not in expected address range (size=%d, data=%p not in %p..%p)\n",
pctx->name,
status->size,
status->data,
ctx->ipc_buf->vaddr,
ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1);
return -EINVAL;
}
/* build rpmsg message */
build_msg_header(ctx, DELTA_IPC_DECODE, &msg.header);
msg.param_size = param->size;
msg.param_paddr = to_paddr(ctx, param->data);
msg.status_size = status->size;
msg.status_paddr = to_paddr(ctx, status->data);
/* send it */
ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
if (ret) {
dev_err(delta->dev,
"%s ipc: failed to decode, rpmsg_send failed (%d) for DELTA_IPC_DECODE (size=%d, data=%p)\n",
pctx->name,
ret, param->size, param->data);
pctx->sys_errors++;
return ret;
}
/* wait for acknowledge */
if (!wait_for_completion_timeout
(&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
dev_err(delta->dev,
"%s ipc: failed to decode, timeout waiting for DELTA_IPC_DECODE callback (size=%d, data=%p)\n",
pctx->name,
param->size, param->data);
pctx->sys_errors++;
return -ETIMEDOUT;
}
/* command completed, check status */
if (ctx->cb_err) {
dev_err(delta->dev,
"%s ipc: failed to decode, DELTA_IPC_DECODE completed but with error (%d) (size=%d, data=%p)\n",
pctx->name,
ctx->cb_err, param->size, param->data);
pctx->sys_errors++;
return -EIO;
}
return 0;
};
void delta_ipc_close(void *hdl)
{
struct delta_ipc_ctx *ctx = to_ctx(hdl);
struct delta_ctx *pctx = to_pctx(ctx);
struct delta_dev *delta = pctx->dev;
struct rpmsg_device *rpmsg_device = delta->rpmsg_device;
struct delta_ipc_close_msg msg;
int ret;
if (!hdl) {
dev_err(delta->dev,
"%s ipc: failed to close, invalid ipc handle\n",
pctx->name);
return;
}
if (ctx->ipc_buf) {
hw_free(pctx, ctx->ipc_buf);
ctx->ipc_buf = NULL;
}
if (!rpmsg_device) {
dev_err(delta->dev,
"%s ipc: failed to close, rpmsg is not initialized\n",
pctx->name);
return;
}
/* build rpmsg message */
build_msg_header(ctx, DELTA_IPC_CLOSE, &msg.header);
/* send it */
ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg));
if (ret) {
dev_err(delta->dev,
"%s ipc: failed to close, rpmsg_send failed (%d) for DELTA_IPC_CLOSE\n",
pctx->name, ret);
pctx->sys_errors++;
return;
}
/* wait for acknowledge */
if (!wait_for_completion_timeout
(&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) {
dev_err(delta->dev,
"%s ipc: failed to close, timeout waiting for DELTA_IPC_CLOSE callback\n",
pctx->name);
pctx->sys_errors++;
return;
}
/* command completed, check status */
if (ctx->cb_err) {
dev_err(delta->dev,
"%s ipc: failed to close, DELTA_IPC_CLOSE completed but with error (%d)\n",
pctx->name, ctx->cb_err);
pctx->sys_errors++;
}
};
static int delta_ipc_cb(struct rpmsg_device *rpdev, void *data,
int len, void *priv, u32 src)
{
struct delta_ipc_ctx *ctx;
struct delta_ipc_cb_msg *msg;
/* sanity check */
if (!rpdev) {
dev_err(NULL, "rpdev is NULL\n");
return -EINVAL;
}
if (!data || !len) {
dev_err(&rpdev->dev,
"unexpected empty message received from src=%d\n", src);
return -EINVAL;
}
if (len != sizeof(*msg)) {
dev_err(&rpdev->dev,
"unexpected message length received from src=%d (received %d bytes while %zu bytes expected)\n",
len, src, sizeof(*msg));
return -EINVAL;
}
msg = (struct delta_ipc_cb_msg *)data;
if (msg->header.tag != IPC_SANITY_TAG) {
dev_err(&rpdev->dev,
"unexpected message tag received from src=%d (received %x tag while %x expected)\n",
src, msg->header.tag, IPC_SANITY_TAG);
return -EINVAL;
}
ctx = msg_to_ctx(msg);
if (!ctx) {
dev_err(&rpdev->dev,
"unexpected message with NULL host_hdl received from src=%d\n",
src);
return -EINVAL;
}
/*
* if not already known, save copro instance context
* to ensure re-entrance on copro side
*/
if (!ctx->copro_hdl)
ctx->copro_hdl = msg_to_copro_hdl(msg);
/*
* all is fine,
* update status & complete command
*/
ctx->cb_err = msg->err;
complete(&ctx->done);
return 0;
}
static int delta_ipc_probe(struct rpmsg_device *rpmsg_device)
{
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver);
struct delta_dev *delta = to_delta(rpdrv);
delta->rpmsg_device = rpmsg_device;
return 0;
}
static void delta_ipc_remove(struct rpmsg_device *rpmsg_device)
{
struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver);
struct delta_dev *delta = to_delta(rpdrv);
delta->rpmsg_device = NULL;
}
static struct rpmsg_device_id delta_ipc_device_id_table[] = {
{.name = "rpmsg-delta"},
{},
};
static struct rpmsg_driver delta_rpmsg_driver = {
.drv = {.name = KBUILD_MODNAME},
.id_table = delta_ipc_device_id_table,
.probe = delta_ipc_probe,
.callback = delta_ipc_cb,
.remove = delta_ipc_remove,
};
int delta_ipc_init(struct delta_dev *delta)
{
delta->rpmsg_driver = delta_rpmsg_driver;
return register_rpmsg_driver(&delta->rpmsg_driver);
}
void delta_ipc_exit(struct delta_dev *delta)
{
unregister_rpmsg_driver(&delta->rpmsg_driver);
}
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_IPC_H
#define DELTA_IPC_H
int delta_ipc_init(struct delta_dev *delta);
void delta_ipc_exit(struct delta_dev *delta);
/*
* delta_ipc_open - open a decoding instance on firmware side
* @ctx: (in) delta context
* @name: (in) name of decoder to be used
* @param: (in) open command parameters specific to decoder
* @param.size: (in) size of parameter
* @param.data: (in) virtual address of parameter
* @ipc_buf_size: (in) size of IPC shared buffer between host
* and copro used to share command data.
* Client have to set here the size of the biggest
* command parameters (+ status if any).
* Allocation will be done in this function which
* will give back to client in @ipc_buf the virtual
* & physical addresses & size of shared IPC buffer.
* All the further command data (parameters + status)
* have to be written in this shared IPC buffer
* virtual memory. This is done to avoid
* unnecessary copies of command data.
* @ipc_buf: (out) allocated IPC shared buffer
* @ipc_buf.size: (out) allocated size
* @ipc_buf.vaddr: (out) virtual address where to copy
* further command data
* @hdl: (out) handle of decoding instance.
*/
int delta_ipc_open(struct delta_ctx *ctx, const char *name,
struct delta_ipc_param *param, u32 ipc_buf_size,
struct delta_buf **ipc_buf, void **hdl);
/*
* delta_ipc_set_stream - set information about stream to decoder
* @hdl: (in) handle of decoding instance.
* @param: (in) set stream command parameters specific to decoder
* @param.size: (in) size of parameter
* @param.data: (in) virtual address of parameter. Must be
* within IPC shared buffer range
*/
int delta_ipc_set_stream(void *hdl, struct delta_ipc_param *param);
/*
* delta_ipc_decode - frame decoding synchronous request, returns only
* after decoding completion on firmware side.
* @hdl: (in) handle of decoding instance.
* @param: (in) decode command parameters specific to decoder
* @param.size: (in) size of parameter
* @param.data: (in) virtual address of parameter. Must be
* within IPC shared buffer range
* @status: (in/out) decode command status specific to decoder
* @status.size: (in) size of status
* @status.data: (in/out) virtual address of status. Must be
* within IPC shared buffer range.
* Status is filled by decoding instance
* after decoding completion.
*/
int delta_ipc_decode(void *hdl, struct delta_ipc_param *param,
struct delta_ipc_param *status);
/*
* delta_ipc_close - close decoding instance
* @hdl: (in) handle of decoding instance to close.
*/
void delta_ipc_close(void *hdl);
#endif /* DELTA_IPC_H */
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#include "delta.h"
#include "delta-mem.h"
int hw_alloc(struct delta_ctx *ctx, u32 size, const char *name,
struct delta_buf *buf)
{
struct delta_dev *delta = ctx->dev;
dma_addr_t dma_addr;
void *addr;
unsigned long attrs = DMA_ATTR_WRITE_COMBINE;
addr = dma_alloc_attrs(delta->dev, size, &dma_addr,
GFP_KERNEL | __GFP_NOWARN, attrs);
if (!addr) {
dev_err(delta->dev,
"%s hw_alloc:dma_alloc_coherent failed for %s (size=%d)\n",
ctx->name, name, size);
ctx->sys_errors++;
return -ENOMEM;
}
buf->size = size;
buf->paddr = dma_addr;
buf->vaddr = addr;
buf->name = name;
buf->attrs = attrs;
dev_dbg(delta->dev,
"%s allocate %d bytes of HW memory @(virt=0x%p, phy=0x%pad): %s\n",
ctx->name, size, buf->vaddr, &buf->paddr, buf->name);
return 0;
}
void hw_free(struct delta_ctx *ctx, struct delta_buf *buf)
{
struct delta_dev *delta = ctx->dev;
dev_dbg(delta->dev,
"%s free %d bytes of HW memory @(virt=0x%p, phy=0x%pad): %s\n",
ctx->name, buf->size, buf->vaddr, &buf->paddr, buf->name);
dma_free_attrs(delta->dev, buf->size,
buf->vaddr, buf->paddr, buf->attrs);
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_MEM_H
#define DELTA_MEM_H
int hw_alloc(struct delta_ctx *ctx, u32 size, const char *name,
struct delta_buf *buf);
void hw_free(struct delta_ctx *ctx, struct delta_buf *buf);
#endif /* DELTA_MEM_H */
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2013
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#include <linux/slab.h>
#include "delta.h"
#include "delta-ipc.h"
#include "delta-mjpeg.h"
#include "delta-mjpeg-fw.h"
#define DELTA_MJPEG_MAX_RESO DELTA_MAX_RESO
struct delta_mjpeg_ctx {
/* jpeg header */
struct mjpeg_header header_struct;
struct mjpeg_header *header;
/* ipc */
void *ipc_hdl;
struct delta_buf *ipc_buf;
/* decoded output frame */
struct delta_frame *out_frame;
unsigned char str[3000];
};
#define to_ctx(ctx) ((struct delta_mjpeg_ctx *)(ctx)->priv)
static char *ipc_open_param_str(struct jpeg_video_decode_init_params_t *p,
char *str, unsigned int len)
{
char *b = str;
if (!p)
return "";
b += snprintf(b, len,
"jpeg_video_decode_init_params_t\n"
"circular_buffer_begin_addr_p 0x%x\n"
"circular_buffer_end_addr_p 0x%x\n",
p->circular_buffer_begin_addr_p,
p->circular_buffer_end_addr_p);
return str;
}
static char *ipc_decode_param_str(struct jpeg_decode_params_t *p,
char *str, unsigned int len)
{
char *b = str;
if (!p)
return "";
b += snprintf(b, len,
"jpeg_decode_params_t\n"
"picture_start_addr_p 0x%x\n"
"picture_end_addr_p 0x%x\n"
"decoding_mode %d\n"
"display_buffer_addr.display_decimated_luma_p 0x%x\n"
"display_buffer_addr.display_decimated_chroma_p 0x%x\n"
"main_aux_enable %d\n"
"additional_flags 0x%x\n"
"field_flag %x\n"
"is_jpeg_image %x\n",
p->picture_start_addr_p,
p->picture_end_addr_p,
p->decoding_mode,
p->display_buffer_addr.display_decimated_luma_p,
p->display_buffer_addr.display_decimated_chroma_p,
p->main_aux_enable, p->additional_flags,
p->field_flag,
p->is_jpeg_image);
return str;
}
static inline bool is_stream_error(enum jpeg_decoding_error_t err)
{
switch (err) {
case JPEG_DECODER_UNDEFINED_HUFF_TABLE:
case JPEG_DECODER_BAD_RESTART_MARKER:
case JPEG_DECODER_BAD_SOS_SPECTRAL:
case JPEG_DECODER_BAD_SOS_SUCCESSIVE:
case JPEG_DECODER_BAD_HEADER_LENGTH:
case JPEG_DECODER_BAD_COUNT_VALUE:
case JPEG_DECODER_BAD_DHT_MARKER:
case JPEG_DECODER_BAD_INDEX_VALUE:
case JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES:
case JPEG_DECODER_BAD_QUANT_TABLE_LENGTH:
case JPEG_DECODER_BAD_NUMBER_QUANT_TABLES:
case JPEG_DECODER_BAD_COMPONENT_COUNT:
return true;
default:
return false;
}
}
static inline const char *err_str(enum jpeg_decoding_error_t err)
{
switch (err) {
case JPEG_DECODER_NO_ERROR:
return "JPEG_DECODER_NO_ERROR";
case JPEG_DECODER_UNDEFINED_HUFF_TABLE:
return "JPEG_DECODER_UNDEFINED_HUFF_TABLE";
case JPEG_DECODER_UNSUPPORTED_MARKER:
return "JPEG_DECODER_UNSUPPORTED_MARKER";
case JPEG_DECODER_UNABLE_ALLOCATE_MEMORY:
return "JPEG_DECODER_UNABLE_ALLOCATE_MEMORY";
case JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS:
return "JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS";
case JPEG_DECODER_BAD_PARAMETER:
return "JPEG_DECODER_BAD_PARAMETER";
case JPEG_DECODER_DECODE_ERROR:
return "JPEG_DECODER_DECODE_ERROR";
case JPEG_DECODER_BAD_RESTART_MARKER:
return "JPEG_DECODER_BAD_RESTART_MARKER";
case JPEG_DECODER_UNSUPPORTED_COLORSPACE:
return "JPEG_DECODER_UNSUPPORTED_COLORSPACE";
case JPEG_DECODER_BAD_SOS_SPECTRAL:
return "JPEG_DECODER_BAD_SOS_SPECTRAL";
case JPEG_DECODER_BAD_SOS_SUCCESSIVE:
return "JPEG_DECODER_BAD_SOS_SUCCESSIVE";
case JPEG_DECODER_BAD_HEADER_LENGTH:
return "JPEG_DECODER_BAD_HEADER_LENGTH";
case JPEG_DECODER_BAD_COUNT_VALUE:
return "JPEG_DECODER_BAD_COUNT_VALUE";
case JPEG_DECODER_BAD_DHT_MARKER:
return "JPEG_DECODER_BAD_DHT_MARKER";
case JPEG_DECODER_BAD_INDEX_VALUE:
return "JPEG_DECODER_BAD_INDEX_VALUE";
case JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES:
return "JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES";
case JPEG_DECODER_BAD_QUANT_TABLE_LENGTH:
return "JPEG_DECODER_BAD_QUANT_TABLE_LENGTH";
case JPEG_DECODER_BAD_NUMBER_QUANT_TABLES:
return "JPEG_DECODER_BAD_NUMBER_QUANT_TABLES";
case JPEG_DECODER_BAD_COMPONENT_COUNT:
return "JPEG_DECODER_BAD_COMPONENT_COUNT";
case JPEG_DECODER_DIVIDE_BY_ZERO_ERROR:
return "JPEG_DECODER_DIVIDE_BY_ZERO_ERROR";
case JPEG_DECODER_NOT_JPG_IMAGE:
return "JPEG_DECODER_NOT_JPG_IMAGE";
case JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE:
return "JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE";
case JPEG_DECODER_UNSUPPORTED_SCALING:
return "JPEG_DECODER_UNSUPPORTED_SCALING";
case JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE:
return "JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE";
case JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE:
return "JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE";
case JPEG_DECODER_BAD_VALUE_FROM_RED:
return "JPEG_DECODER_BAD_VALUE_FROM_RED";
case JPEG_DECODER_BAD_SUBREGION_PARAMETERS:
return "JPEG_DECODER_BAD_SUBREGION_PARAMETERS";
case JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED:
return "JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED";
case JPEG_DECODER_ERROR_TASK_TIMEOUT:
return "JPEG_DECODER_ERROR_TASK_TIMEOUT";
case JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED:
return "JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED";
default:
return "!unknown MJPEG error!";
}
}
static bool delta_mjpeg_check_status(struct delta_ctx *pctx,
struct jpeg_decode_return_params_t *status)
{
struct delta_dev *delta = pctx->dev;
bool dump = false;
if (status->error_code == JPEG_DECODER_NO_ERROR)
goto out;
if (is_stream_error(status->error_code)) {
dev_warn_ratelimited(delta->dev,
"%s firmware: stream error @ frame %d (%s)\n",
pctx->name, pctx->decoded_frames,
err_str(status->error_code));
pctx->stream_errors++;
} else {
dev_warn_ratelimited(delta->dev,
"%s firmware: decode error @ frame %d (%s)\n",
pctx->name, pctx->decoded_frames,
err_str(status->error_code));
pctx->decode_errors++;
dump = true;
}
out:
dev_dbg(delta->dev,
"%s firmware: decoding time(us)=%d\n", pctx->name,
status->decode_time_in_us);
return dump;
}
static int delta_mjpeg_ipc_open(struct delta_ctx *pctx)
{
struct delta_dev *delta = pctx->dev;
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
int ret = 0;
struct jpeg_video_decode_init_params_t params_struct;
struct jpeg_video_decode_init_params_t *params = &params_struct;
struct delta_buf *ipc_buf;
u32 ipc_buf_size;
struct delta_ipc_param ipc_param;
void *hdl;
memset(params, 0, sizeof(*params));
params->circular_buffer_begin_addr_p = 0x00000000;
params->circular_buffer_end_addr_p = 0xffffffff;
dev_vdbg(delta->dev,
"%s %s\n", pctx->name,
ipc_open_param_str(params, ctx->str, sizeof(ctx->str)));
ipc_param.size = sizeof(*params);
ipc_param.data = params;
ipc_buf_size = sizeof(struct jpeg_decode_params_t) +
sizeof(struct jpeg_decode_return_params_t);
ret = delta_ipc_open(pctx, "JPEG_DECODER_HW0", &ipc_param,
ipc_buf_size, &ipc_buf, &hdl);
if (ret) {
dev_err(delta->dev,
"%s dumping command %s\n", pctx->name,
ipc_open_param_str(params, ctx->str, sizeof(ctx->str)));
return ret;
}
ctx->ipc_buf = ipc_buf;
ctx->ipc_hdl = hdl;
return 0;
}
static int delta_mjpeg_ipc_decode(struct delta_ctx *pctx, dma_addr_t pstart, dma_addr_t pend)
{
struct delta_dev *delta = pctx->dev;
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
int ret = 0;
struct jpeg_decode_params_t *params = ctx->ipc_buf->vaddr;
struct jpeg_decode_return_params_t *status =
ctx->ipc_buf->vaddr + sizeof(*params);
struct delta_frame *frame;
struct delta_ipc_param ipc_param, ipc_status;
ret = delta_get_free_frame(pctx, &frame);
if (ret)
return ret;
memset(params, 0, sizeof(*params));
params->picture_start_addr_p = pstart;
params->picture_end_addr_p = pend;
/*
* !WARNING!
* the NV12 decoded frame is only available
* on decimated output when enabling flag
* "JPEG_ADDITIONAL_FLAG_420MB"...
* the non decimated output gives YUV422SP
*/
params->main_aux_enable = JPEG_DISP_AUX_EN;
params->additional_flags = JPEG_ADDITIONAL_FLAG_420MB;
params->horizontal_decimation_factor = JPEG_HDEC_1;
params->vertical_decimation_factor = JPEG_VDEC_1;
params->decoding_mode = JPEG_NORMAL_DECODE;
params->display_buffer_addr.struct_size =
sizeof(struct jpeg_display_buffer_address_t);
params->display_buffer_addr.display_decimated_luma_p =
(u32)frame->paddr;
params->display_buffer_addr.display_decimated_chroma_p =
(u32)(frame->paddr
+ frame->info.aligned_width * frame->info.aligned_height);
dev_vdbg(delta->dev,
"%s %s\n", pctx->name,
ipc_decode_param_str(params, ctx->str, sizeof(ctx->str)));
/* status */
memset(status, 0, sizeof(*status));
status->error_code = JPEG_DECODER_NO_ERROR;
ipc_param.size = sizeof(*params);
ipc_param.data = params;
ipc_status.size = sizeof(*status);
ipc_status.data = status;
ret = delta_ipc_decode(ctx->ipc_hdl, &ipc_param, &ipc_status);
if (ret) {
dev_err(delta->dev,
"%s dumping command %s\n", pctx->name,
ipc_decode_param_str(params, ctx->str,
sizeof(ctx->str)));
return ret;
}
pctx->decoded_frames++;
/* check firmware decoding status */
if (delta_mjpeg_check_status(pctx, status)) {
dev_err(delta->dev,
"%s dumping command %s\n", pctx->name,
ipc_decode_param_str(params, ctx->str,
sizeof(ctx->str)));
}
frame->field = V4L2_FIELD_NONE;
frame->flags = V4L2_BUF_FLAG_KEYFRAME;
frame->state |= DELTA_FRAME_DEC;
ctx->out_frame = frame;
return 0;
}
static int delta_mjpeg_open(struct delta_ctx *pctx)
{
struct delta_mjpeg_ctx *ctx;
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
pctx->priv = ctx;
return 0;
}
static int delta_mjpeg_close(struct delta_ctx *pctx)
{
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
if (ctx->ipc_hdl) {
delta_ipc_close(ctx->ipc_hdl);
ctx->ipc_hdl = NULL;
}
kfree(ctx);
return 0;
}
static int delta_mjpeg_get_streaminfo(struct delta_ctx *pctx,
struct delta_streaminfo *streaminfo)
{
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
if (!ctx->header)
goto nodata;
streaminfo->streamformat = V4L2_PIX_FMT_MJPEG;
streaminfo->width = ctx->header->frame_width;
streaminfo->height = ctx->header->frame_height;
/* progressive stream */
streaminfo->field = V4L2_FIELD_NONE;
streaminfo->dpb = 1;
return 0;
nodata:
return -ENODATA;
}
static int delta_mjpeg_decode(struct delta_ctx *pctx, struct delta_au *pau)
{
struct delta_dev *delta = pctx->dev;
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
int ret;
void *au_vaddr = pau->vaddr;
dma_addr_t au_dma = pau->paddr;
size_t au_size = pau->size;
unsigned int data_offset = 0;
struct mjpeg_header *header = &ctx->header_struct;
if (!ctx->header) {
ret = delta_mjpeg_read_header(pctx, au_vaddr, au_size,
header, &data_offset);
if (ret) {
pctx->stream_errors++;
goto err;
}
if (header->frame_width * header->frame_height >
DELTA_MJPEG_MAX_RESO) {
dev_err(delta->dev,
"%s stream resolution too large: %dx%d > %d pixels budget\n",
pctx->name,
header->frame_width,
header->frame_height, DELTA_MJPEG_MAX_RESO);
ret = -EINVAL;
goto err;
}
ctx->header = header;
goto out;
}
if (!ctx->ipc_hdl) {
ret = delta_mjpeg_ipc_open(pctx);
if (ret)
goto err;
}
ret = delta_mjpeg_read_header(pctx, au_vaddr, au_size,
ctx->header, &data_offset);
if (ret) {
pctx->stream_errors++;
goto err;
}
au_dma += data_offset;
au_vaddr += data_offset;
ret = delta_mjpeg_ipc_decode(pctx, au_dma, au_dma + au_size - 1);
if (ret)
goto err;
out:
return 0;
err:
return ret;
}
static int delta_mjpeg_get_frame(struct delta_ctx *pctx,
struct delta_frame **frame)
{
struct delta_mjpeg_ctx *ctx = to_ctx(pctx);
if (!ctx->out_frame)
return -ENODATA;
*frame = ctx->out_frame;
ctx->out_frame = NULL;
return 0;
}
const struct delta_dec mjpegdec = {
.name = "MJPEG",
.streamformat = V4L2_PIX_FMT_MJPEG,
.pixelformat = V4L2_PIX_FMT_NV12,
.open = delta_mjpeg_open,
.close = delta_mjpeg_close,
.get_streaminfo = delta_mjpeg_get_streaminfo,
.get_frameinfo = delta_get_frameinfo_default,
.decode = delta_mjpeg_decode,
.get_frame = delta_mjpeg_get_frame,
.recycle = delta_recycle_default,
};
@@ -0,0 +1,225 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_MJPEG_FW_H
#define DELTA_MJPEG_FW_H
/*
* struct jpeg_decoded_buffer_address_t
*
* defines the addresses where the decoded picture/additional
* info related to the block structures will be stored
*
* @display_luma_p: address of the luma buffer
* @display_chroma_p: address of the chroma buffer
*/
struct jpeg_decoded_buffer_address_t {
u32 luma_p;
u32 chroma_p;
};
/*
* struct jpeg_display_buffer_address_t
*
* defines the addresses (used by the Display Reconstruction block)
* where the pictures to be displayed will be stored
*
* @struct_size: size of the structure in bytes
* @display_luma_p: address of the luma buffer
* @display_chroma_p: address of the chroma buffer
* @display_decimated_luma_p: address of the decimated luma buffer
* @display_decimated_chroma_p: address of the decimated chroma buffer
*/
struct jpeg_display_buffer_address_t {
u32 struct_size;
u32 display_luma_p;
u32 display_chroma_p;
u32 display_decimated_luma_p;
u32 display_decimated_chroma_p;
};
/*
* used for enabling main/aux outputs for both display &
* reference reconstruction blocks
*/
enum jpeg_rcn_ref_disp_enable_t {
/* enable decimated (for display) reconstruction */
JPEG_DISP_AUX_EN = 0x00000010,
/* enable main (for display) reconstruction */
JPEG_DISP_MAIN_EN = 0x00000020,
/* enable both main & decimated (for display) reconstruction */
JPEG_DISP_AUX_MAIN_EN = 0x00000030,
/* enable only reference output(ex. for trick modes) */
JPEG_REF_MAIN_EN = 0x00000100,
/*
* enable reference output with decimated
* (for display) reconstruction
*/
JPEG_REF_MAIN_DISP_AUX_EN = 0x00000110,
/*
* enable reference output with main
* (for display) reconstruction
*/
JPEG_REF_MAIN_DISP_MAIN_EN = 0x00000120,
/*
* enable reference output with main & decimated
* (for display) reconstruction
*/
JPEG_REF_MAIN_DISP_MAIN_AUX_EN = 0x00000130
};
/* identifies the horizontal decimation factor */
enum jpeg_horizontal_deci_factor_t {
/* no resize */
JPEG_HDEC_1 = 0x00000000,
/* Advanced H/2 resize using improved 8-tap filters */
JPEG_HDEC_ADVANCED_2 = 0x00000101,
/* Advanced H/4 resize using improved 8-tap filters */
JPEG_HDEC_ADVANCED_4 = 0x00000102
};
/* identifies the vertical decimation factor */
enum jpeg_vertical_deci_factor_t {
/* no resize */
JPEG_VDEC_1 = 0x00000000,
/* V/2 , progressive resize */
JPEG_VDEC_ADVANCED_2_PROG = 0x00000204,
/* V/2 , interlaced resize */
JPEG_VDEC_ADVANCED_2_INT = 0x000000208
};
/* status of the decoding process */
enum jpeg_decoding_error_t {
JPEG_DECODER_NO_ERROR = 0,
JPEG_DECODER_UNDEFINED_HUFF_TABLE = 1,
JPEG_DECODER_UNSUPPORTED_MARKER = 2,
JPEG_DECODER_UNABLE_ALLOCATE_MEMORY = 3,
JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS = 4,
JPEG_DECODER_BAD_PARAMETER = 5,
JPEG_DECODER_DECODE_ERROR = 6,
JPEG_DECODER_BAD_RESTART_MARKER = 7,
JPEG_DECODER_UNSUPPORTED_COLORSPACE = 8,
JPEG_DECODER_BAD_SOS_SPECTRAL = 9,
JPEG_DECODER_BAD_SOS_SUCCESSIVE = 10,
JPEG_DECODER_BAD_HEADER_LENGTH = 11,
JPEG_DECODER_BAD_COUNT_VALUE = 12,
JPEG_DECODER_BAD_DHT_MARKER = 13,
JPEG_DECODER_BAD_INDEX_VALUE = 14,
JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES = 15,
JPEG_DECODER_BAD_QUANT_TABLE_LENGTH = 16,
JPEG_DECODER_BAD_NUMBER_QUANT_TABLES = 17,
JPEG_DECODER_BAD_COMPONENT_COUNT = 18,
JPEG_DECODER_DIVIDE_BY_ZERO_ERROR = 19,
JPEG_DECODER_NOT_JPG_IMAGE = 20,
JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE = 21,
JPEG_DECODER_UNSUPPORTED_SCALING = 22,
JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE = 23,
JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE = 24,
JPEG_DECODER_BAD_VALUE_FROM_RED = 25,
JPEG_DECODER_BAD_SUBREGION_PARAMETERS = 26,
JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED = 27,
JPEG_DECODER_ERROR_TASK_TIMEOUT = 28,
JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED = 29
};
/* identifies the decoding mode */
enum jpeg_decoding_mode_t {
JPEG_NORMAL_DECODE = 0,
};
enum jpeg_additional_flags_t {
JPEG_ADDITIONAL_FLAG_NONE = 0,
/* request firmware to return values of the CEH registers */
JPEG_ADDITIONAL_FLAG_CEH = 1,
/* output storage of auxiliary reconstruction in Raster format. */
JPEG_ADDITIONAL_FLAG_RASTER = 64,
/* output storage of auxiliary reconstruction in 420MB format. */
JPEG_ADDITIONAL_FLAG_420MB = 128
};
/*
* struct jpeg_video_decode_init_params_t - initialization command parameters
*
* @circular_buffer_begin_addr_p: start address of fw circular buffer
* @circular_buffer_end_addr_p: end address of fw circular buffer
*/
struct jpeg_video_decode_init_params_t {
u32 circular_buffer_begin_addr_p;
u32 circular_buffer_end_addr_p;
u32 reserved;
};
/*
* struct jpeg_decode_params_t - decode command parameters
*
* @picture_start_addr_p: start address of jpeg picture
* @picture_end_addr_p: end address of jpeg picture
* @decoded_buffer_addr: decoded picture buffer
* @display_buffer_addr: display picture buffer
* @main_aux_enable: enable main and/or aux outputs
* @horizontal_decimation_factor:horizontal decimation factor
* @vertical_decimation_factor: vertical decimation factor
* @xvalue0: the x(0) coordinate for subregion decoding
* @xvalue1: the x(1) coordinate for subregion decoding
* @yvalue0: the y(0) coordinate for subregion decoding
* @yvalue1: the y(1) coordinate for subregion decoding
* @decoding_mode: decoding mode
* @additional_flags: additional flags
* @field_flag: determines frame/field scan
* @is_jpeg_image: 1 = still jpeg, 0 = motion jpeg
*/
struct jpeg_decode_params_t {
u32 picture_start_addr_p;
u32 picture_end_addr_p;
struct jpeg_decoded_buffer_address_t decoded_buffer_addr;
struct jpeg_display_buffer_address_t display_buffer_addr;
enum jpeg_rcn_ref_disp_enable_t main_aux_enable;
enum jpeg_horizontal_deci_factor_t horizontal_decimation_factor;
enum jpeg_vertical_deci_factor_t vertical_decimation_factor;
u32 xvalue0;
u32 xvalue1;
u32 yvalue0;
u32 yvalue1;
enum jpeg_decoding_mode_t decoding_mode;
u32 additional_flags;
u32 field_flag;
u32 reserved;
u32 is_jpeg_image;
};
/*
* struct jpeg_decode_return_params_t
*
* status returned by firmware after decoding
*
* @decode_time_in_us: decoding time in microseconds
* @pm_cycles: profiling information
* @pm_dmiss: profiling information
* @pm_imiss: profiling information
* @pm_bundles: profiling information
* @pm_pft: profiling information
* @error_code: status of the decoding process
* @ceh_registers: array where values of the Contrast Enhancement
* Histogram (CEH) registers will be stored.
* ceh_registers[0] correspond to register MBE_CEH_0_7,
* ceh_registers[1] correspond to register MBE_CEH_8_15
* ceh_registers[2] correspond to register MBE_CEH_16_23
* Note that elements of this array will be updated only
* if additional_flags has JPEG_ADDITIONAL_FLAG_CEH set.
*/
struct jpeg_decode_return_params_t {
/* profiling info */
u32 decode_time_in_us;
u32 pm_cycles;
u32 pm_dmiss;
u32 pm_imiss;
u32 pm_bundles;
u32 pm_pft;
enum jpeg_decoding_error_t error_code;
u32 ceh_registers[32];
};
#endif /* DELTA_MJPEG_FW_H */
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2013
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#include "delta.h"
#include "delta-mjpeg.h"
#define MJPEG_SOF_0 0xc0
#define MJPEG_SOF_1 0xc1
#define MJPEG_SOI 0xd8
#define MJPEG_MARKER 0xff
static char *header_str(struct mjpeg_header *header,
char *str,
unsigned int len)
{
char *cur = str;
unsigned int left = len;
if (!header)
return "";
snprintf(cur, left, "[MJPEG header]\n"
"|- length = %d\n"
"|- precision = %d\n"
"|- width = %d\n"
"|- height = %d\n"
"|- components = %d\n",
header->length,
header->sample_precision,
header->frame_width,
header->frame_height,
header->nb_of_components);
return str;
}
static int delta_mjpeg_read_sof(struct delta_ctx *pctx,
unsigned char *data, unsigned int size,
struct mjpeg_header *header)
{
struct delta_dev *delta = pctx->dev;
unsigned int offset = 0;
if (size < 64)
goto err_no_more;
memset(header, 0, sizeof(*header));
header->length = be16_to_cpu(*(__be16 *)(data + offset));
offset += sizeof(u16);
header->sample_precision = *(u8 *)(data + offset);
offset += sizeof(u8);
header->frame_height = be16_to_cpu(*(__be16 *)(data + offset));
offset += sizeof(u16);
header->frame_width = be16_to_cpu(*(__be16 *)(data + offset));
offset += sizeof(u16);
header->nb_of_components = *(u8 *)(data + offset);
offset += sizeof(u8);
if (header->nb_of_components >= MJPEG_MAX_COMPONENTS) {
dev_err(delta->dev,
"%s unsupported number of components (%d > %d)\n",
pctx->name, header->nb_of_components,
MJPEG_MAX_COMPONENTS);
return -EINVAL;
}
if ((offset + header->nb_of_components *
sizeof(header->components[0])) > size)
goto err_no_more;
return 0;
err_no_more:
dev_err(delta->dev,
"%s sof: reached end of %d size input stream\n",
pctx->name, size);
return -ENODATA;
}
int delta_mjpeg_read_header(struct delta_ctx *pctx,
unsigned char *data, unsigned int size,
struct mjpeg_header *header,
unsigned int *data_offset)
{
struct delta_dev *delta = pctx->dev;
unsigned char str[200];
unsigned int ret = 0;
unsigned int offset = 0;
unsigned int soi = 0;
if (size < 2)
goto err_no_more;
offset = 0;
while (1) {
if (data[offset] == MJPEG_MARKER)
switch (data[offset + 1]) {
case MJPEG_SOI:
soi = 1;
*data_offset = offset;
break;
case MJPEG_SOF_0:
case MJPEG_SOF_1:
if (!soi) {
dev_err(delta->dev,
"%s wrong sequence, got SOF while SOI not seen\n",
pctx->name);
return -EINVAL;
}
ret = delta_mjpeg_read_sof(pctx,
&data[offset + 2],
size - (offset + 2),
header);
if (ret)
goto err;
goto done;
default:
break;
}
offset++;
if ((offset + 2) >= size)
goto err_no_more;
}
done:
dev_dbg(delta->dev,
"%s found header @ offset %d:\n%s", pctx->name,
*data_offset,
header_str(header, str, sizeof(str)));
return 0;
err_no_more:
dev_err(delta->dev,
"%s no header found within %d bytes input stream\n",
pctx->name, size);
return -ENODATA;
err:
return ret;
}
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2013
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_MJPEG_H
#define DELTA_MJPEG_H
#include "delta.h"
struct mjpeg_component {
unsigned int id;/* 1=Y, 2=Cb, 3=Cr, 4=L, 5=Q */
unsigned int h_sampling_factor;
unsigned int v_sampling_factor;
unsigned int quant_table_index;
};
#define MJPEG_MAX_COMPONENTS 5
struct mjpeg_header {
unsigned int length;
unsigned int sample_precision;
unsigned int frame_width;
unsigned int frame_height;
unsigned int nb_of_components;
struct mjpeg_component components[MJPEG_MAX_COMPONENTS];
};
int delta_mjpeg_read_header(struct delta_ctx *pctx,
unsigned char *data, unsigned int size,
struct mjpeg_header *header,
unsigned int *data_offset);
#endif /* DELTA_MJPEG_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,566 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
*/
#ifndef DELTA_H
#define DELTA_H
#include <linux/rpmsg.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mem2mem.h>
#include "delta-cfg.h"
/*
* enum delta_state - state of decoding instance
*
*@DELTA_STATE_WF_FORMAT:
* Wait for compressed format to be set by V4L2 client in order
* to know what is the relevant decoder to open.
*
*@DELTA_STATE_WF_STREAMINFO:
* Wait for stream information to be available (bitstream
* header parsing is done).
*
*@DELTA_STATE_READY:
* Decoding instance is ready to decode compressed access unit.
*
*@DELTA_STATE_WF_EOS:
* Decoding instance is waiting for EOS (End Of Stream) completion.
*
*@DELTA_STATE_EOS:
* EOS (End Of Stream) is completed (signaled to user). Decoding instance
* should then be closed.
*/
enum delta_state {
DELTA_STATE_WF_FORMAT,
DELTA_STATE_WF_STREAMINFO,
DELTA_STATE_READY,
DELTA_STATE_WF_EOS,
DELTA_STATE_EOS
};
/*
* struct delta_streaminfo - information about stream to decode
*
* @flags: validity of fields (crop, pixelaspect, other)
* @width: width of video stream
* @height: height ""
* @streamformat: fourcc compressed format of video (MJPEG, MPEG2, ...)
* @dpb: number of frames needed to decode a single frame
* (h264 dpb, up to 16)
* @crop: cropping window inside decoded frame (1920x1080@0,0
* inside 1920x1088 frame for ex.)
* @pixelaspect: pixel aspect ratio of video (4/3, 5/4)
* @field: interlaced or not
* @profile: profile string
* @level: level string
* @other: other string information from codec
* @colorspace: colorspace identifier
* @xfer_func: transfer function identifier
* @ycbcr_enc: Y'CbCr encoding identifier
* @quantization: quantization identifier
*/
struct delta_streaminfo {
u32 flags;
u32 streamformat;
u32 width;
u32 height;
u32 dpb;
struct v4l2_rect crop;
struct v4l2_fract pixelaspect;
enum v4l2_field field;
u8 profile[32];
u8 level[32];
u8 other[32];
enum v4l2_colorspace colorspace;
enum v4l2_xfer_func xfer_func;
enum v4l2_ycbcr_encoding ycbcr_enc;
enum v4l2_quantization quantization;
};
#define DELTA_STREAMINFO_FLAG_CROP 0x0001
#define DELTA_STREAMINFO_FLAG_PIXELASPECT 0x0002
#define DELTA_STREAMINFO_FLAG_OTHER 0x0004
/*
* struct delta_au - access unit structure.
*
* @vbuf: video buffer information for V4L2
* @list: V4L2 m2m list that the frame belongs to
* @prepared: if set vaddr/paddr are resolved
* @vaddr: virtual address (kernel can read/write)
* @paddr: physical address (for hardware)
* @flags: access unit type (V4L2_BUF_FLAG_KEYFRAME/PFRAME/BFRAME)
* @dts: decoding timestamp of this access unit
*/
struct delta_au {
struct vb2_v4l2_buffer vbuf; /* keep first */
struct list_head list; /* keep second */
bool prepared;
u32 size;
void *vaddr;
dma_addr_t paddr;
u32 flags;
u64 dts;
};
/*
* struct delta_frameinfo - information about decoded frame
*
* @flags: validity of fields (crop, pixelaspect)
* @pixelformat: fourcc code for uncompressed video format
* @width: width of frame
* @height: height of frame
* @aligned_width: width of frame (with encoder or decoder alignment
* constraint)
* @aligned_height: height of frame (with encoder or decoder alignment
* constraint)
* @size: maximum size in bytes required for data
* @crop: cropping window inside frame (1920x1080@0,0
* inside 1920x1088 frame for ex.)
* @pixelaspect: pixel aspect ratio of video (4/3, 5/4)
* @field: interlaced mode
* @colorspace: colorspace identifier
* @xfer_func: transfer function identifier
* @ycbcr_enc: Y'CbCr encoding identifier
* @quantization: quantization identifier
*/
struct delta_frameinfo {
u32 flags;
u32 pixelformat;
u32 width;
u32 height;
u32 aligned_width;
u32 aligned_height;
u32 size;
struct v4l2_rect crop;
struct v4l2_fract pixelaspect;
enum v4l2_field field;
enum v4l2_colorspace colorspace;
enum v4l2_xfer_func xfer_func;
enum v4l2_ycbcr_encoding ycbcr_enc;
enum v4l2_quantization quantization;
};
#define DELTA_FRAMEINFO_FLAG_CROP 0x0001
#define DELTA_FRAMEINFO_FLAG_PIXELASPECT 0x0002
/*
* struct delta_frame - frame structure.
*
* @vbuf: video buffer information for V4L2
* @list: V4L2 m2m list that the frame belongs to
* @info: frame information (width, height, format, alignment...)
* @prepared: if set pix/vaddr/paddr are resolved
* @index: frame index, aligned on V4L2 wow
* @vaddr: virtual address (kernel can read/write)
* @paddr: physical address (for hardware)
* @state: frame state for frame lifecycle tracking
* (DELTA_FRAME_FREE/DEC/OUT/REC/...)
* @flags: frame type (V4L2_BUF_FLAG_KEYFRAME/PFRAME/BFRAME)
* @dts: decoding timestamp of this frame
* @field: field order for interlaced frame
*/
struct delta_frame {
struct vb2_v4l2_buffer vbuf; /* keep first */
struct list_head list; /* keep second */
struct delta_frameinfo info;
bool prepared;
u32 index;
void *vaddr;
dma_addr_t paddr;
u32 state;
u32 flags;
u64 dts;
enum v4l2_field field;
};
/* frame state for frame lifecycle tracking */
#define DELTA_FRAME_FREE 0x00 /* is free and can be used for decoding */
#define DELTA_FRAME_REF 0x01 /* is a reference frame */
#define DELTA_FRAME_BSY 0x02 /* is owned by decoder and busy */
#define DELTA_FRAME_DEC 0x04 /* contains decoded content */
#define DELTA_FRAME_OUT 0x08 /* has been given to user */
#define DELTA_FRAME_RDY 0x10 /* is ready but still held by decoder */
#define DELTA_FRAME_M2M 0x20 /* is owned by mem2mem framework */
/*
* struct delta_dts - decoding timestamp.
*
* @list: list to chain timestamps
* @val: timestamp in microseconds
*/
struct delta_dts {
struct list_head list;
u64 val;
};
struct delta_buf {
u32 size;
void *vaddr;
dma_addr_t paddr;
const char *name;
unsigned long attrs;
};
struct delta_ipc_ctx {
int cb_err;
u32 copro_hdl;
struct completion done;
struct delta_buf ipc_buf_struct;
struct delta_buf *ipc_buf;
};
struct delta_ipc_param {
u32 size;
void *data;
};
struct delta_ctx;
/*
* struct delta_dec - decoder structure.
*
* @name: name of this decoder
* @streamformat: input stream format that this decoder support
* @pixelformat: pixel format of decoded frame that this decoder support
* @max_width: (optional) maximum width that can decode this decoder
* if not set, maximum width is DELTA_MAX_WIDTH
* @max_height: (optional) maximum height that can decode this decoder
* if not set, maximum height is DELTA_MAX_HEIGHT
* @pm: (optional) if set, decoder will manage power on its own
* @open: open this decoder
* @close: close this decoder
* @setup_frame: setup frame to be used by decoder, see below
* @get_streaminfo: get stream related infos, see below
* @get_frameinfo: get decoded frame related infos, see below
* @set_frameinfo: (optional) set decoded frame related infos, see below
* @setup_frame: setup frame to be used by decoder, see below
* @decode: decode a single access unit, see below
* @get_frame: get the next decoded frame available, see below
* @recycle: recycle the given frame, see below
* @flush: (optional) flush decoder, see below
* @drain: (optional) drain decoder, see below
*/
struct delta_dec {
const char *name;
u32 streamformat;
u32 pixelformat;
u32 max_width;
u32 max_height;
bool pm;
/*
* decoder ops
*/
int (*open)(struct delta_ctx *ctx);
int (*close)(struct delta_ctx *ctx);
/*
* setup_frame() - setup frame to be used by decoder
* @ctx: (in) instance
* @frame: (in) frame to use
* @frame.index (in) identifier of frame
* @frame.vaddr (in) virtual address (kernel can read/write)
* @frame.paddr (in) physical address (for hardware)
*
* Frame is to be allocated by caller, then given
* to decoder through this call.
* Several frames must be given to decoder (dpb),
* each frame is identified using its index.
*/
int (*setup_frame)(struct delta_ctx *ctx, struct delta_frame *frame);
/*
* get_streaminfo() - get stream related infos
* @ctx: (in) instance
* @streaminfo: (out) width, height, dpb,...
*
* Precondition: stream header must have been successfully
* parsed to have this call successful & @streaminfo valid.
* Header parsing must be done using decode(), giving
* explicitly header access unit or first access unit of bitstream.
* If no valid header is found, get_streaminfo will return -ENODATA,
* in this case the next bitstream access unit must be decoded till
* get_streaminfo becomes successful.
*/
int (*get_streaminfo)(struct delta_ctx *ctx,
struct delta_streaminfo *streaminfo);
/*
* get_frameinfo() - get decoded frame related infos
* @ctx: (in) instance
* @frameinfo: (out) width, height, alignment, crop, ...
*
* Precondition: get_streaminfo() must be successful
*/
int (*get_frameinfo)(struct delta_ctx *ctx,
struct delta_frameinfo *frameinfo);
/*
* set_frameinfo() - set decoded frame related infos
* @ctx: (in) instance
* @frameinfo: (out) width, height, alignment, crop, ...
*
* Optional.
* Typically used to negotiate with decoder the output
* frame if decoder can do post-processing.
*/
int (*set_frameinfo)(struct delta_ctx *ctx,
struct delta_frameinfo *frameinfo);
/*
* decode() - decode a single access unit
* @ctx: (in) instance
* @au: (in/out) access unit
* @au.size (in) size of au to decode
* @au.vaddr (in) virtual address (kernel can read/write)
* @au.paddr (in) physical address (for hardware)
* @au.flags (out) au type (V4L2_BUF_FLAG_KEYFRAME/
* PFRAME/BFRAME)
*
* Decode the access unit given. Decode is synchronous;
* access unit memory is no more needed after this call.
* After this call, none, one or several frames could
* have been decoded, which can be retrieved using
* get_frame().
*/
int (*decode)(struct delta_ctx *ctx, struct delta_au *au);
/*
* get_frame() - get the next decoded frame available
* @ctx: (in) instance
* @frame: (out) frame with decoded data:
* @frame.index (out) identifier of frame
* @frame.field (out) field order for interlaced frame
* @frame.state (out) frame state for frame lifecycle tracking
* @frame.flags (out) frame type (V4L2_BUF_FLAG_KEYFRAME/
* PFRAME/BFRAME)
*
* Get the next available decoded frame.
* If no frame is available, -ENODATA is returned.
* If a frame is available, frame structure is filled with
* relevant data, frame.index identifying this exact frame.
* When this frame is no more needed by upper layers,
* recycle() must be called giving this frame identifier.
*/
int (*get_frame)(struct delta_ctx *ctx, struct delta_frame **frame);
/*
* recycle() - recycle the given frame
* @ctx: (in) instance
* @frame: (in) frame to recycle:
* @frame.index (in) identifier of frame
*
* recycle() is to be called by user when the decoded frame
* is no more needed (composition/display done).
* This frame will then be reused by decoder to proceed
* with next frame decoding.
* If not enough frames have been provided through setup_frame(),
* or recycle() is not called fast enough, the decoder can run out
* of available frames to proceed with decoding (starvation).
* This case is guarded by wq_recycle wait queue which ensures that
* decoder is called only if at least one frame is available.
*/
int (*recycle)(struct delta_ctx *ctx, struct delta_frame *frame);
/*
* flush() - flush decoder
* @ctx: (in) instance
*
* Optional.
* Reset decoder context and discard all internal buffers.
* This allows implementation of seek, which leads to discontinuity
* of input bitstream that decoder must know to restart its internal
* decoding logic.
*/
int (*flush)(struct delta_ctx *ctx);
/*
* drain() - drain decoder
* @ctx: (in) instance
*
* Optional.
* Mark decoder pending frames (decoded but not yet output) as ready
* so that they can be output to client at EOS (End Of Stream).
* get_frame() is to be called in a loop right after drain() to
* get all those pending frames.
*/
int (*drain)(struct delta_ctx *ctx);
};
struct delta_dev;
/*
* struct delta_ctx - instance structure.
*
* @flags: validity of fields (streaminfo)
* @fh: V4L2 file handle
* @dev: device context
* @dec: selected decoder context for this instance
* @ipc_ctx: context of IPC communication with firmware
* @state: instance state
* @frame_num: frame number
* @au_num: access unit number
* @max_au_size: max size of an access unit
* @streaminfo: stream information (width, height, dpb, interlacing...)
* @frameinfo: frame information (width, height, format, alignment...)
* @nb_of_frames: number of frames available for decoding
* @frames: array of decoding frames to keep track of frame
* state and manage frame recycling
* @decoded_frames: nb of decoded frames from opening
* @output_frames: nb of output frames from opening
* @dropped_frames: nb of frames dropped (ie access unit not parsed
* or frame decoded but not output)
* @stream_errors: nb of stream errors (corrupted, not supported, ...)
* @decode_errors: nb of decode errors (firmware error)
* @sys_errors: nb of system errors (memory, ipc, ...)
* @dts: FIFO of decoding timestamp.
* output frames are timestamped with incoming access
* unit timestamps using this fifo.
* @name: string naming this instance (debug purpose)
* @run_work: decoding work
* @lock: lock for decoding work serialization
* @aborting: true if current job aborted
* @priv: private decoder context for this instance, allocated
* by decoder @open time.
*/
struct delta_ctx {
u32 flags;
struct v4l2_fh fh;
struct delta_dev *dev;
const struct delta_dec *dec;
struct delta_ipc_ctx ipc_ctx;
enum delta_state state;
u32 frame_num;
u32 au_num;
size_t max_au_size;
struct delta_streaminfo streaminfo;
struct delta_frameinfo frameinfo;
u32 nb_of_frames;
struct delta_frame *frames[DELTA_MAX_FRAMES];
u32 decoded_frames;
u32 output_frames;
u32 dropped_frames;
u32 stream_errors;
u32 decode_errors;
u32 sys_errors;
struct list_head dts;
char name[100];
struct work_struct run_work;
struct mutex lock;
bool aborting;
void *priv;
};
#define DELTA_FLAG_STREAMINFO 0x0001
#define DELTA_FLAG_FRAMEINFO 0x0002
#define DELTA_MAX_FORMATS DELTA_MAX_DECODERS
/*
* struct delta_dev - device struct, 1 per probe (so single one for
* all platform life)
*
* @v4l2_dev: v4l2 device
* @vdev: v4l2 video device
* @pdev: platform device
* @dev: device
* @m2m_dev: memory-to-memory V4L2 device
* @lock: device lock, for crit section & V4L2 ops serialization.
* @clk_delta: delta main clock
* @clk_st231: st231 coprocessor main clock
* @clk_flash_promip: flash promip clock
* @decoders: list of registered decoders
* @nb_of_decoders: nb of registered decoders
* @pixelformats: supported uncompressed video formats
* @nb_of_pixelformats: number of supported umcompressed video formats
* @streamformats: supported compressed video formats
* @nb_of_streamformats:number of supported compressed video formats
* @instance_id: rolling counter identifying an instance (debug purpose)
* @work_queue: decoding job work queue
* @rpmsg_driver: rpmsg IPC driver
* @rpmsg_device: rpmsg IPC device
*/
struct delta_dev {
struct v4l2_device v4l2_dev;
struct video_device *vdev;
struct platform_device *pdev;
struct device *dev;
struct v4l2_m2m_dev *m2m_dev;
struct mutex lock;
struct clk *clk_delta;
struct clk *clk_st231;
struct clk *clk_flash_promip;
const struct delta_dec *decoders[DELTA_MAX_DECODERS];
u32 nb_of_decoders;
u32 pixelformats[DELTA_MAX_FORMATS];
u32 nb_of_pixelformats;
u32 streamformats[DELTA_MAX_FORMATS];
u32 nb_of_streamformats;
u8 instance_id;
struct workqueue_struct *work_queue;
struct rpmsg_driver rpmsg_driver;
struct rpmsg_device *rpmsg_device;
};
static inline char *frame_type_str(u32 flags)
{
if (flags & V4L2_BUF_FLAG_KEYFRAME)
return "I";
if (flags & V4L2_BUF_FLAG_PFRAME)
return "P";
if (flags & V4L2_BUF_FLAG_BFRAME)
return "B";
if (flags & V4L2_BUF_FLAG_LAST)
return "EOS";
return "?";
}
static inline char *frame_field_str(enum v4l2_field field)
{
if (field == V4L2_FIELD_NONE)
return "-";
if (field == V4L2_FIELD_TOP)
return "T";
if (field == V4L2_FIELD_BOTTOM)
return "B";
if (field == V4L2_FIELD_INTERLACED)
return "I";
if (field == V4L2_FIELD_INTERLACED_TB)
return "TB";
if (field == V4L2_FIELD_INTERLACED_BT)
return "BT";
return "?";
}
static inline char *frame_state_str(u32 state, char *str, unsigned int len)
{
snprintf(str, len, "%s %s %s %s %s %s",
(state & DELTA_FRAME_REF) ? "ref" : " ",
(state & DELTA_FRAME_BSY) ? "bsy" : " ",
(state & DELTA_FRAME_DEC) ? "dec" : " ",
(state & DELTA_FRAME_OUT) ? "out" : " ",
(state & DELTA_FRAME_M2M) ? "m2m" : " ",
(state & DELTA_FRAME_RDY) ? "rdy" : " ");
return str;
}
int delta_get_frameinfo_default(struct delta_ctx *ctx,
struct delta_frameinfo *frameinfo);
int delta_recycle_default(struct delta_ctx *pctx,
struct delta_frame *frame);
int delta_get_free_frame(struct delta_ctx *ctx,
struct delta_frame **pframe);
int delta_get_sync(struct delta_ctx *ctx);
void delta_put_autosuspend(struct delta_ctx *ctx);
#endif /* DELTA_H */
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_STI_HVA
tristate "STMicroelectronics HVA multi-format video encoder V4L2 driver"
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_STI || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select V4L2_MEM2MEM_DEV
help
This V4L2 driver enables HVA (Hardware Video Accelerator) multi-format
video encoder of STMicroelectronics SoC, allowing hardware encoding of
raw uncompressed formats in various compressed video bitstreams format.
To compile this driver as a module, choose M here:
the module will be called st-hva.
config VIDEO_STI_HVA_DEBUGFS
bool "Export STMicroelectronics HVA internals in debugfs"
depends on VIDEO_STI_HVA
depends on DEBUG_FS
help
Select this to see information about the internal state and the last
operation of STMicroelectronics HVA multi-format video encoder in
debugfs.
Choose N unless you know you need this.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO_STI_HVA) += st-hva.o
st-hva-y := hva-v4l2.o hva-hw.o hva-mem.o hva-h264.o
st-hva-$(CONFIG_VIDEO_STI_HVA_DEBUGFS) += hva-debugfs.o
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#include <linux/debugfs.h>
#include "hva.h"
#include "hva-hw.h"
static void format_ctx(struct seq_file *s, struct hva_ctx *ctx)
{
struct hva_streaminfo *stream = &ctx->streaminfo;
struct hva_frameinfo *frame = &ctx->frameinfo;
struct hva_controls *ctrls = &ctx->ctrls;
struct hva_ctx_dbg *dbg = &ctx->dbg;
u32 bitrate_mode, aspect, entropy, vui_sar, sei_fp;
seq_printf(s, "|-%s\n |\n", ctx->name);
seq_printf(s, " |-[%sframe info]\n",
ctx->flags & HVA_FLAG_FRAMEINFO ? "" : "default ");
seq_printf(s, " | |- pixel format=%4.4s\n"
" | |- wxh=%dx%d\n"
" | |- wxh (w/ encoder alignment constraint)=%dx%d\n"
" |\n",
(char *)&frame->pixelformat,
frame->width, frame->height,
frame->aligned_width, frame->aligned_height);
seq_printf(s, " |-[%sstream info]\n",
ctx->flags & HVA_FLAG_STREAMINFO ? "" : "default ");
seq_printf(s, " | |- stream format=%4.4s\n"
" | |- wxh=%dx%d\n"
" | |- %s\n"
" | |- %s\n"
" |\n",
(char *)&stream->streamformat,
stream->width, stream->height,
stream->profile, stream->level);
bitrate_mode = V4L2_CID_MPEG_VIDEO_BITRATE_MODE;
aspect = V4L2_CID_MPEG_VIDEO_ASPECT;
seq_puts(s, " |-[parameters]\n");
seq_printf(s, " | |- %s\n"
" | |- bitrate=%d bps\n"
" | |- GOP size=%d\n"
" | |- video aspect=%s\n"
" | |- framerate=%d/%d\n",
v4l2_ctrl_get_menu(bitrate_mode)[ctrls->bitrate_mode],
ctrls->bitrate,
ctrls->gop_size,
v4l2_ctrl_get_menu(aspect)[ctrls->aspect],
ctrls->time_per_frame.denominator,
ctrls->time_per_frame.numerator);
entropy = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE;
vui_sar = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC;
sei_fp = V4L2_CID_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE;
if (stream->streamformat == V4L2_PIX_FMT_H264) {
seq_printf(s, " | |- %s entropy mode\n"
" | |- CPB size=%d kB\n"
" | |- DCT8x8 enable=%s\n"
" | |- qpmin=%d\n"
" | |- qpmax=%d\n"
" | |- PAR enable=%s\n"
" | |- PAR id=%s\n"
" | |- SEI frame packing enable=%s\n"
" | |- SEI frame packing type=%s\n",
v4l2_ctrl_get_menu(entropy)[ctrls->entropy_mode],
ctrls->cpb_size,
ctrls->dct8x8 ? "true" : "false",
ctrls->qpmin,
ctrls->qpmax,
ctrls->vui_sar ? "true" : "false",
v4l2_ctrl_get_menu(vui_sar)[ctrls->vui_sar_idc],
ctrls->sei_fp ? "true" : "false",
v4l2_ctrl_get_menu(sei_fp)[ctrls->sei_fp_type]);
}
if (ctx->sys_errors || ctx->encode_errors || ctx->frame_errors) {
seq_puts(s, " |\n |-[errors]\n");
seq_printf(s, " | |- system=%d\n"
" | |- encoding=%d\n"
" | |- frame=%d\n",
ctx->sys_errors,
ctx->encode_errors,
ctx->frame_errors);
}
seq_puts(s, " |\n |-[performances]\n");
seq_printf(s, " | |- frames encoded=%d\n"
" | |- avg HW processing duration (0.1ms)=%d [min=%d, max=%d]\n"
" | |- avg encoding period (0.1ms)=%d [min=%d, max=%d]\n"
" | |- avg fps (0.1Hz)=%d\n"
" | |- max reachable fps (0.1Hz)=%d\n"
" | |- avg bitrate (kbps)=%d [min=%d, max=%d]\n"
" | |- last bitrate (kbps)=%d\n",
dbg->cnt_duration,
dbg->avg_duration,
dbg->min_duration,
dbg->max_duration,
dbg->avg_period,
dbg->min_period,
dbg->max_period,
dbg->avg_fps,
dbg->max_fps,
dbg->avg_bitrate,
dbg->min_bitrate,
dbg->max_bitrate,
dbg->last_bitrate);
}
/*
* performance debug info
*/
void hva_dbg_perf_begin(struct hva_ctx *ctx)
{
u64 div;
u32 period;
u32 bitrate;
struct hva_ctx_dbg *dbg = &ctx->dbg;
ktime_t prev = dbg->begin;
dbg->begin = ktime_get();
if (dbg->is_valid_period) {
/* encoding period */
div = (u64)ktime_us_delta(dbg->begin, prev);
do_div(div, 100);
period = (u32)div;
dbg->min_period = min(period, dbg->min_period);
dbg->max_period = max(period, dbg->max_period);
dbg->total_period += period;
dbg->cnt_period++;
/*
* minimum and maximum bitrates are based on the
* encoding period values upon a window of 32 samples
*/
dbg->window_duration += period;
dbg->cnt_window++;
if (dbg->cnt_window >= 32) {
/*
* bitrate in kbps = (size * 8 / 1000) /
* (duration / 10000)
* = size * 80 / duration
*/
if (dbg->window_duration > 0) {
div = (u64)dbg->window_stream_size * 80;
do_div(div, dbg->window_duration);
bitrate = (u32)div;
dbg->last_bitrate = bitrate;
dbg->min_bitrate = min(bitrate,
dbg->min_bitrate);
dbg->max_bitrate = max(bitrate,
dbg->max_bitrate);
}
dbg->window_stream_size = 0;
dbg->window_duration = 0;
dbg->cnt_window = 0;
}
}
/*
* filter sequences valid for performance:
* - begin/begin (no stream available) is an invalid sequence
* - begin/end is a valid sequence
*/
dbg->is_valid_period = false;
}
void hva_dbg_perf_end(struct hva_ctx *ctx, struct hva_stream *stream)
{
struct device *dev = ctx_to_dev(ctx);
u64 div;
u32 duration;
u32 bytesused;
u32 timestamp;
struct hva_ctx_dbg *dbg = &ctx->dbg;
ktime_t end = ktime_get();
/* stream bytesused and timestamp in us */
bytesused = vb2_get_plane_payload(&stream->vbuf.vb2_buf, 0);
div = stream->vbuf.vb2_buf.timestamp;
do_div(div, 1000);
timestamp = (u32)div;
/* encoding duration */
div = (u64)ktime_us_delta(end, dbg->begin);
dev_dbg(dev,
"%s perf stream[%d] dts=%d encoded using %d bytes in %d us",
ctx->name,
stream->vbuf.sequence,
timestamp,
bytesused, (u32)div);
do_div(div, 100);
duration = (u32)div;
dbg->min_duration = min(duration, dbg->min_duration);
dbg->max_duration = max(duration, dbg->max_duration);
dbg->total_duration += duration;
dbg->cnt_duration++;
/*
* the average bitrate is based on the total stream size
* and the total encoding periods
*/
dbg->total_stream_size += bytesused;
dbg->window_stream_size += bytesused;
dbg->is_valid_period = true;
}
static void hva_dbg_perf_compute(struct hva_ctx *ctx)
{
u64 div;
struct hva_ctx_dbg *dbg = &ctx->dbg;
if (dbg->cnt_duration > 0) {
div = (u64)dbg->total_duration;
do_div(div, dbg->cnt_duration);
dbg->avg_duration = (u32)div;
} else {
dbg->avg_duration = 0;
}
if (dbg->total_duration > 0) {
div = (u64)dbg->cnt_duration * 100000;
do_div(div, dbg->total_duration);
dbg->max_fps = (u32)div;
} else {
dbg->max_fps = 0;
}
if (dbg->cnt_period > 0) {
div = (u64)dbg->total_period;
do_div(div, dbg->cnt_period);
dbg->avg_period = (u32)div;
} else {
dbg->avg_period = 0;
}
if (dbg->total_period > 0) {
div = (u64)dbg->cnt_period * 100000;
do_div(div, dbg->total_period);
dbg->avg_fps = (u32)div;
} else {
dbg->avg_fps = 0;
}
if (dbg->total_period > 0) {
/*
* bitrate in kbps = (video size * 8 / 1000) /
* (video duration / 10000)
* = video size * 80 / video duration
*/
div = (u64)dbg->total_stream_size * 80;
do_div(div, dbg->total_period);
dbg->avg_bitrate = (u32)div;
} else {
dbg->avg_bitrate = 0;
}
}
/*
* device debug info
*/
static int device_show(struct seq_file *s, void *data)
{
struct hva_dev *hva = s->private;
seq_printf(s, "[%s]\n", hva->v4l2_dev.name);
seq_printf(s, "registered as /dev/video%d\n", hva->vdev->num);
return 0;
}
static int encoders_show(struct seq_file *s, void *data)
{
struct hva_dev *hva = s->private;
unsigned int i = 0;
seq_printf(s, "[encoders]\n|- %d registered encoders:\n",
hva->nb_of_encoders);
while (hva->encoders[i]) {
seq_printf(s, "|- %s: %4.4s => %4.4s\n", hva->encoders[i]->name,
(char *)&hva->encoders[i]->pixelformat,
(char *)&hva->encoders[i]->streamformat);
i++;
}
return 0;
}
static int last_show(struct seq_file *s, void *data)
{
struct hva_dev *hva = s->private;
struct hva_ctx *last_ctx = &hva->dbg.last_ctx;
if (last_ctx->flags & HVA_FLAG_STREAMINFO) {
seq_puts(s, "[last encoding]\n");
hva_dbg_perf_compute(last_ctx);
format_ctx(s, last_ctx);
} else {
seq_puts(s, "[no information recorded about last encoding]\n");
}
return 0;
}
static int regs_show(struct seq_file *s, void *data)
{
struct hva_dev *hva = s->private;
hva_hw_dump_regs(hva, s);
return 0;
}
#define hva_dbg_create_entry(name) \
debugfs_create_file(#name, 0444, hva->dbg.debugfs_entry, hva, \
&name##_fops)
DEFINE_SHOW_ATTRIBUTE(device);
DEFINE_SHOW_ATTRIBUTE(encoders);
DEFINE_SHOW_ATTRIBUTE(last);
DEFINE_SHOW_ATTRIBUTE(regs);
void hva_debugfs_create(struct hva_dev *hva)
{
hva->dbg.debugfs_entry = debugfs_create_dir(HVA_NAME, NULL);
hva_dbg_create_entry(device);
hva_dbg_create_entry(encoders);
hva_dbg_create_entry(last);
hva_dbg_create_entry(regs);
}
void hva_debugfs_remove(struct hva_dev *hva)
{
debugfs_remove_recursive(hva->dbg.debugfs_entry);
hva->dbg.debugfs_entry = NULL;
}
/*
* context (instance) debug info
*/
static int ctx_show(struct seq_file *s, void *data)
{
struct hva_ctx *ctx = s->private;
seq_printf(s, "[running encoding %d]\n", ctx->id);
hva_dbg_perf_compute(ctx);
format_ctx(s, ctx);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(ctx);
void hva_dbg_ctx_create(struct hva_ctx *ctx)
{
struct hva_dev *hva = ctx->hva_dev;
char name[4] = "";
ctx->dbg.min_duration = UINT_MAX;
ctx->dbg.min_period = UINT_MAX;
ctx->dbg.min_bitrate = UINT_MAX;
snprintf(name, sizeof(name), "%d", hva->instance_id);
ctx->dbg.debugfs_entry = debugfs_create_file(name, 0444,
hva->dbg.debugfs_entry,
ctx, &ctx_fops);
}
void hva_dbg_ctx_remove(struct hva_ctx *ctx)
{
struct hva_dev *hva = ctx->hva_dev;
if (ctx->flags & HVA_FLAG_STREAMINFO)
/* save context before removing */
memcpy(&hva->dbg.last_ctx, ctx, sizeof(*ctx));
debugfs_remove(ctx->dbg.debugfs_entry);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,584 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
#include <linux/seq_file.h>
#endif
#include "hva.h"
#include "hva-hw.h"
/* HVA register offsets */
#define HVA_HIF_REG_RST 0x0100U
#define HVA_HIF_REG_RST_ACK 0x0104U
#define HVA_HIF_REG_MIF_CFG 0x0108U
#define HVA_HIF_REG_HEC_MIF_CFG 0x010CU
#define HVA_HIF_REG_CFL 0x0110U
#define HVA_HIF_FIFO_CMD 0x0114U
#define HVA_HIF_FIFO_STS 0x0118U
#define HVA_HIF_REG_SFL 0x011CU
#define HVA_HIF_REG_IT_ACK 0x0120U
#define HVA_HIF_REG_ERR_IT_ACK 0x0124U
#define HVA_HIF_REG_LMI_ERR 0x0128U
#define HVA_HIF_REG_EMI_ERR 0x012CU
#define HVA_HIF_REG_HEC_MIF_ERR 0x0130U
#define HVA_HIF_REG_HEC_STS 0x0134U
#define HVA_HIF_REG_HVC_STS 0x0138U
#define HVA_HIF_REG_HJE_STS 0x013CU
#define HVA_HIF_REG_CNT 0x0140U
#define HVA_HIF_REG_HEC_CHKSYN_DIS 0x0144U
#define HVA_HIF_REG_CLK_GATING 0x0148U
#define HVA_HIF_REG_VERSION 0x014CU
#define HVA_HIF_REG_BSM 0x0150U
/* define value for version id register (HVA_HIF_REG_VERSION) */
#define VERSION_ID_MASK 0x0000FFFF
/* define values for BSM register (HVA_HIF_REG_BSM) */
#define BSM_CFG_VAL1 0x0003F000
#define BSM_CFG_VAL2 0x003F0000
/* define values for memory interface register (HVA_HIF_REG_MIF_CFG) */
#define MIF_CFG_VAL1 0x04460446
#define MIF_CFG_VAL2 0x04460806
#define MIF_CFG_VAL3 0x00000000
/* define value for HEC memory interface register (HVA_HIF_REG_MIF_CFG) */
#define HEC_MIF_CFG_VAL 0x000000C4
/* Bits definition for clock gating register (HVA_HIF_REG_CLK_GATING) */
#define CLK_GATING_HVC BIT(0)
#define CLK_GATING_HEC BIT(1)
#define CLK_GATING_HJE BIT(2)
/* fix hva clock rate */
#define CLK_RATE 300000000
/* fix delay for pmruntime */
#define AUTOSUSPEND_DELAY_MS 3
/*
* hw encode error values
* NO_ERROR: Success, Task OK
* H264_BITSTREAM_OVERSIZE: VECH264 Bitstream size > bitstream buffer
* H264_FRAME_SKIPPED: VECH264 Frame skipped (refers to CPB Buffer Size)
* H264_SLICE_LIMIT_SIZE: VECH264 MB > slice limit size
* H264_MAX_SLICE_NUMBER: VECH264 max slice number reached
* H264_SLICE_READY: VECH264 Slice ready
* TASK_LIST_FULL: HVA/FPC task list full
(discard latest transform command)
* UNKNOWN_COMMAND: Transform command not known by HVA/FPC
* WRONG_CODEC_OR_RESOLUTION: Wrong Codec or Resolution Selection
* NO_INT_COMPLETION: Time-out on interrupt completion
* LMI_ERR: Local Memory Interface Error
* EMI_ERR: External Memory Interface Error
* HECMI_ERR: HEC Memory Interface Error
*/
enum hva_hw_error {
NO_ERROR = 0x0,
H264_BITSTREAM_OVERSIZE = 0x2,
H264_FRAME_SKIPPED = 0x4,
H264_SLICE_LIMIT_SIZE = 0x5,
H264_MAX_SLICE_NUMBER = 0x7,
H264_SLICE_READY = 0x8,
TASK_LIST_FULL = 0xF0,
UNKNOWN_COMMAND = 0xF1,
WRONG_CODEC_OR_RESOLUTION = 0xF4,
NO_INT_COMPLETION = 0x100,
LMI_ERR = 0x101,
EMI_ERR = 0x102,
HECMI_ERR = 0x103,
};
static irqreturn_t hva_hw_its_interrupt(int irq, void *data)
{
struct hva_dev *hva = data;
/* read status registers */
hva->sts_reg = readl_relaxed(hva->regs + HVA_HIF_FIFO_STS);
hva->sfl_reg = readl_relaxed(hva->regs + HVA_HIF_REG_SFL);
/* acknowledge interruption */
writel_relaxed(0x1, hva->regs + HVA_HIF_REG_IT_ACK);
return IRQ_WAKE_THREAD;
}
static irqreturn_t hva_hw_its_irq_thread(int irq, void *arg)
{
struct hva_dev *hva = arg;
struct device *dev = hva_to_dev(hva);
u32 status = hva->sts_reg & 0xFF;
u8 ctx_id = 0;
struct hva_ctx *ctx = NULL;
dev_dbg(dev, "%s %s: status: 0x%02x fifo level: 0x%02x\n",
HVA_PREFIX, __func__, hva->sts_reg & 0xFF, hva->sfl_reg & 0xF);
/*
* status: task_id[31:16] client_id[15:8] status[7:0]
* the context identifier is retrieved from the client identifier
*/
ctx_id = (hva->sts_reg & 0xFF00) >> 8;
if (ctx_id >= HVA_MAX_INSTANCES) {
dev_err(dev, "%s %s: bad context identifier: %d\n",
HVA_PREFIX, __func__, ctx_id);
goto out;
}
ctx = hva->instances[ctx_id];
if (!ctx)
goto out;
switch (status) {
case NO_ERROR:
dev_dbg(dev, "%s %s: no error\n",
ctx->name, __func__);
ctx->hw_err = false;
break;
case H264_SLICE_READY:
dev_dbg(dev, "%s %s: h264 slice ready\n",
ctx->name, __func__);
ctx->hw_err = false;
break;
case H264_FRAME_SKIPPED:
dev_dbg(dev, "%s %s: h264 frame skipped\n",
ctx->name, __func__);
ctx->hw_err = false;
break;
case H264_BITSTREAM_OVERSIZE:
dev_err(dev, "%s %s:h264 bitstream oversize\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
case H264_SLICE_LIMIT_SIZE:
dev_err(dev, "%s %s: h264 slice limit size is reached\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
case H264_MAX_SLICE_NUMBER:
dev_err(dev, "%s %s: h264 max slice number is reached\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
case TASK_LIST_FULL:
dev_err(dev, "%s %s:task list full\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
case UNKNOWN_COMMAND:
dev_err(dev, "%s %s: command not known\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
case WRONG_CODEC_OR_RESOLUTION:
dev_err(dev, "%s %s: wrong codec or resolution\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
default:
dev_err(dev, "%s %s: status not recognized\n",
ctx->name, __func__);
ctx->hw_err = true;
break;
}
out:
complete(&hva->interrupt);
return IRQ_HANDLED;
}
static irqreturn_t hva_hw_err_interrupt(int irq, void *data)
{
struct hva_dev *hva = data;
/* read status registers */
hva->sts_reg = readl_relaxed(hva->regs + HVA_HIF_FIFO_STS);
hva->sfl_reg = readl_relaxed(hva->regs + HVA_HIF_REG_SFL);
/* read error registers */
hva->lmi_err_reg = readl_relaxed(hva->regs + HVA_HIF_REG_LMI_ERR);
hva->emi_err_reg = readl_relaxed(hva->regs + HVA_HIF_REG_EMI_ERR);
hva->hec_mif_err_reg = readl_relaxed(hva->regs +
HVA_HIF_REG_HEC_MIF_ERR);
/* acknowledge interruption */
writel_relaxed(0x1, hva->regs + HVA_HIF_REG_IT_ACK);
return IRQ_WAKE_THREAD;
}
static irqreturn_t hva_hw_err_irq_thread(int irq, void *arg)
{
struct hva_dev *hva = arg;
struct device *dev = hva_to_dev(hva);
u8 ctx_id = 0;
struct hva_ctx *ctx;
dev_dbg(dev, "%s status: 0x%02x fifo level: 0x%02x\n",
HVA_PREFIX, hva->sts_reg & 0xFF, hva->sfl_reg & 0xF);
/*
* status: task_id[31:16] client_id[15:8] status[7:0]
* the context identifier is retrieved from the client identifier
*/
ctx_id = (hva->sts_reg & 0xFF00) >> 8;
if (ctx_id >= HVA_MAX_INSTANCES) {
dev_err(dev, "%s bad context identifier: %d\n", HVA_PREFIX,
ctx_id);
goto out;
}
ctx = hva->instances[ctx_id];
if (!ctx)
goto out;
if (hva->lmi_err_reg) {
dev_err(dev, "%s local memory interface error: 0x%08x\n",
ctx->name, hva->lmi_err_reg);
ctx->hw_err = true;
}
if (hva->emi_err_reg) {
dev_err(dev, "%s external memory interface error: 0x%08x\n",
ctx->name, hva->emi_err_reg);
ctx->hw_err = true;
}
if (hva->hec_mif_err_reg) {
dev_err(dev, "%s hec memory interface error: 0x%08x\n",
ctx->name, hva->hec_mif_err_reg);
ctx->hw_err = true;
}
out:
complete(&hva->interrupt);
return IRQ_HANDLED;
}
static unsigned long int hva_hw_get_ip_version(struct hva_dev *hva)
{
struct device *dev = hva_to_dev(hva);
unsigned long int version;
if (pm_runtime_resume_and_get(dev) < 0) {
dev_err(dev, "%s failed to get pm_runtime\n", HVA_PREFIX);
mutex_unlock(&hva->protect_mutex);
return -EFAULT;
}
version = readl_relaxed(hva->regs + HVA_HIF_REG_VERSION) &
VERSION_ID_MASK;
pm_runtime_put_autosuspend(dev);
switch (version) {
case HVA_VERSION_V400:
dev_dbg(dev, "%s IP hardware version 0x%lx\n",
HVA_PREFIX, version);
break;
default:
dev_err(dev, "%s unknown IP hardware version 0x%lx\n",
HVA_PREFIX, version);
version = HVA_VERSION_UNKNOWN;
break;
}
return version;
}
int hva_hw_probe(struct platform_device *pdev, struct hva_dev *hva)
{
struct device *dev = &pdev->dev;
struct resource *esram;
int ret;
WARN_ON(!hva);
/* get memory for registers */
hva->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(hva->regs)) {
dev_err(dev, "%s failed to get regs\n", HVA_PREFIX);
return PTR_ERR(hva->regs);
}
/* get memory for esram */
esram = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!esram) {
dev_err(dev, "%s failed to get esram\n", HVA_PREFIX);
return -ENODEV;
}
hva->esram_addr = esram->start;
hva->esram_size = resource_size(esram);
dev_info(dev, "%s esram reserved for address: 0x%x size:%d\n",
HVA_PREFIX, hva->esram_addr, hva->esram_size);
/* get clock resource */
hva->clk = devm_clk_get(dev, "clk_hva");
if (IS_ERR(hva->clk)) {
dev_err(dev, "%s failed to get clock\n", HVA_PREFIX);
return PTR_ERR(hva->clk);
}
ret = clk_prepare(hva->clk);
if (ret < 0) {
dev_err(dev, "%s failed to prepare clock\n", HVA_PREFIX);
hva->clk = ERR_PTR(-EINVAL);
return ret;
}
/* get status interruption resource */
ret = platform_get_irq(pdev, 0);
if (ret < 0)
goto err_clk;
hva->irq_its = ret;
ret = devm_request_threaded_irq(dev, hva->irq_its, hva_hw_its_interrupt,
hva_hw_its_irq_thread,
IRQF_ONESHOT,
"hva_its_irq", hva);
if (ret) {
dev_err(dev, "%s failed to install status IRQ 0x%x\n",
HVA_PREFIX, hva->irq_its);
goto err_clk;
}
disable_irq(hva->irq_its);
/* get error interruption resource */
ret = platform_get_irq(pdev, 1);
if (ret < 0)
goto err_clk;
hva->irq_err = ret;
ret = devm_request_threaded_irq(dev, hva->irq_err, hva_hw_err_interrupt,
hva_hw_err_irq_thread,
IRQF_ONESHOT,
"hva_err_irq", hva);
if (ret) {
dev_err(dev, "%s failed to install error IRQ 0x%x\n",
HVA_PREFIX, hva->irq_err);
goto err_clk;
}
disable_irq(hva->irq_err);
/* initialise protection mutex */
mutex_init(&hva->protect_mutex);
/* initialise completion signal */
init_completion(&hva->interrupt);
/* initialise runtime power management */
pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_suspended(dev);
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "%s failed to set PM\n", HVA_PREFIX);
goto err_disable;
}
/* check IP hardware version */
hva->ip_version = hva_hw_get_ip_version(hva);
if (hva->ip_version == HVA_VERSION_UNKNOWN) {
ret = -EINVAL;
goto err_pm;
}
dev_info(dev, "%s found hva device (version 0x%lx)\n", HVA_PREFIX,
hva->ip_version);
return 0;
err_pm:
pm_runtime_put(dev);
err_disable:
pm_runtime_disable(dev);
err_clk:
clk_unprepare(hva->clk);
return ret;
}
void hva_hw_remove(struct hva_dev *hva)
{
struct device *dev = hva_to_dev(hva);
disable_irq(hva->irq_its);
disable_irq(hva->irq_err);
pm_runtime_put_autosuspend(dev);
pm_runtime_disable(dev);
}
int hva_hw_runtime_suspend(struct device *dev)
{
struct hva_dev *hva = dev_get_drvdata(dev);
clk_disable_unprepare(hva->clk);
return 0;
}
int hva_hw_runtime_resume(struct device *dev)
{
struct hva_dev *hva = dev_get_drvdata(dev);
if (clk_prepare_enable(hva->clk)) {
dev_err(hva->dev, "%s failed to prepare hva clk\n",
HVA_PREFIX);
return -EINVAL;
}
if (clk_set_rate(hva->clk, CLK_RATE)) {
dev_err(dev, "%s failed to set clock frequency\n",
HVA_PREFIX);
clk_disable_unprepare(hva->clk);
return -EINVAL;
}
return 0;
}
int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
struct hva_buffer *task)
{
struct hva_dev *hva = ctx_to_hdev(ctx);
struct device *dev = hva_to_dev(hva);
u8 client_id = ctx->id;
int ret;
u32 reg = 0;
bool got_pm = false;
mutex_lock(&hva->protect_mutex);
/* enable irqs */
enable_irq(hva->irq_its);
enable_irq(hva->irq_err);
if (pm_runtime_resume_and_get(dev) < 0) {
dev_err(dev, "%s failed to get pm_runtime\n", ctx->name);
ctx->sys_errors++;
ret = -EFAULT;
goto out;
}
got_pm = true;
reg = readl_relaxed(hva->regs + HVA_HIF_REG_CLK_GATING);
switch (cmd) {
case H264_ENC:
reg |= CLK_GATING_HVC;
break;
default:
dev_dbg(dev, "%s unknown command 0x%x\n", ctx->name, cmd);
ctx->encode_errors++;
ret = -EFAULT;
goto out;
}
writel_relaxed(reg, hva->regs + HVA_HIF_REG_CLK_GATING);
dev_dbg(dev, "%s %s: write configuration registers\n", ctx->name,
__func__);
/* byte swap config */
writel_relaxed(BSM_CFG_VAL1, hva->regs + HVA_HIF_REG_BSM);
/* define Max Opcode Size and Max Message Size for LMI and EMI */
writel_relaxed(MIF_CFG_VAL3, hva->regs + HVA_HIF_REG_MIF_CFG);
writel_relaxed(HEC_MIF_CFG_VAL, hva->regs + HVA_HIF_REG_HEC_MIF_CFG);
/*
* command FIFO: task_id[31:16] client_id[15:8] command_type[7:0]
* the context identifier is provided as client identifier to the
* hardware, and is retrieved in the interrupt functions from the
* status register
*/
dev_dbg(dev, "%s %s: send task (cmd: %d, task_desc: %pad)\n",
ctx->name, __func__, cmd + (client_id << 8), &task->paddr);
writel_relaxed(cmd + (client_id << 8), hva->regs + HVA_HIF_FIFO_CMD);
writel_relaxed(task->paddr, hva->regs + HVA_HIF_FIFO_CMD);
if (!wait_for_completion_timeout(&hva->interrupt,
msecs_to_jiffies(2000))) {
dev_err(dev, "%s %s: time out on completion\n", ctx->name,
__func__);
ctx->encode_errors++;
ret = -EFAULT;
goto out;
}
/* get encoding status */
ret = ctx->hw_err ? -EFAULT : 0;
ctx->encode_errors += ctx->hw_err ? 1 : 0;
out:
disable_irq(hva->irq_its);
disable_irq(hva->irq_err);
switch (cmd) {
case H264_ENC:
reg &= ~CLK_GATING_HVC;
writel_relaxed(reg, hva->regs + HVA_HIF_REG_CLK_GATING);
break;
default:
dev_dbg(dev, "%s unknown command 0x%x\n", ctx->name, cmd);
}
if (got_pm)
pm_runtime_put_autosuspend(dev);
mutex_unlock(&hva->protect_mutex);
return ret;
}
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
#define DUMP(reg) seq_printf(s, "%-30s: 0x%08X\n",\
#reg, readl_relaxed(hva->regs + reg))
void hva_hw_dump_regs(struct hva_dev *hva, struct seq_file *s)
{
struct device *dev = hva_to_dev(hva);
mutex_lock(&hva->protect_mutex);
if (pm_runtime_resume_and_get(dev) < 0) {
seq_puts(s, "Cannot wake up IP\n");
mutex_unlock(&hva->protect_mutex);
return;
}
seq_printf(s, "Registers:\nReg @ = 0x%p\n", hva->regs);
DUMP(HVA_HIF_REG_RST);
DUMP(HVA_HIF_REG_RST_ACK);
DUMP(HVA_HIF_REG_MIF_CFG);
DUMP(HVA_HIF_REG_HEC_MIF_CFG);
DUMP(HVA_HIF_REG_CFL);
DUMP(HVA_HIF_REG_SFL);
DUMP(HVA_HIF_REG_LMI_ERR);
DUMP(HVA_HIF_REG_EMI_ERR);
DUMP(HVA_HIF_REG_HEC_MIF_ERR);
DUMP(HVA_HIF_REG_HEC_STS);
DUMP(HVA_HIF_REG_HVC_STS);
DUMP(HVA_HIF_REG_HJE_STS);
DUMP(HVA_HIF_REG_CNT);
DUMP(HVA_HIF_REG_HEC_CHKSYN_DIS);
DUMP(HVA_HIF_REG_CLK_GATING);
DUMP(HVA_HIF_REG_VERSION);
pm_runtime_put_autosuspend(dev);
mutex_unlock(&hva->protect_mutex);
}
#endif
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#ifndef HVA_HW_H
#define HVA_HW_H
#include "hva-mem.h"
/* HVA Versions */
#define HVA_VERSION_UNKNOWN 0x000
#define HVA_VERSION_V400 0x400
/* HVA command types */
enum hva_hw_cmd_type {
/* RESERVED = 0x00 */
/* RESERVED = 0x01 */
H264_ENC = 0x02,
/* RESERVED = 0x03 */
/* RESERVED = 0x04 */
/* RESERVED = 0x05 */
/* RESERVED = 0x06 */
/* RESERVED = 0x07 */
REMOVE_CLIENT = 0x08,
FREEZE_CLIENT = 0x09,
START_CLIENT = 0x0A,
FREEZE_ALL = 0x0B,
START_ALL = 0x0C,
REMOVE_ALL = 0x0D
};
int hva_hw_probe(struct platform_device *pdev, struct hva_dev *hva);
void hva_hw_remove(struct hva_dev *hva);
int hva_hw_runtime_suspend(struct device *dev);
int hva_hw_runtime_resume(struct device *dev);
int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
struct hva_buffer *task);
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
void hva_hw_dump_regs(struct hva_dev *hva, struct seq_file *s);
#endif
#endif /* HVA_HW_H */
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#include "hva.h"
#include "hva-mem.h"
int hva_mem_alloc(struct hva_ctx *ctx, u32 size, const char *name,
struct hva_buffer **buf)
{
struct device *dev = ctx_to_dev(ctx);
struct hva_buffer *b;
dma_addr_t paddr;
void *base;
b = devm_kzalloc(dev, sizeof(*b), GFP_KERNEL);
if (!b) {
ctx->sys_errors++;
return -ENOMEM;
}
base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL,
DMA_ATTR_WRITE_COMBINE);
if (!base) {
dev_err(dev, "%s %s : dma_alloc_attrs failed for %s (size=%d)\n",
ctx->name, __func__, name, size);
ctx->sys_errors++;
devm_kfree(dev, b);
return -ENOMEM;
}
b->size = size;
b->paddr = paddr;
b->vaddr = base;
b->name = name;
dev_dbg(dev,
"%s allocate %d bytes of HW memory @(virt=%p, phy=%pad): %s\n",
ctx->name, size, b->vaddr, &b->paddr, b->name);
/* return hva buffer to user */
*buf = b;
return 0;
}
void hva_mem_free(struct hva_ctx *ctx, struct hva_buffer *buf)
{
struct device *dev = ctx_to_dev(ctx);
dev_dbg(dev,
"%s free %d bytes of HW memory @(virt=%p, phy=%pad): %s\n",
ctx->name, buf->size, buf->vaddr, &buf->paddr, buf->name);
dma_free_attrs(dev, buf->size, buf->vaddr, buf->paddr,
DMA_ATTR_WRITE_COMBINE);
devm_kfree(dev, buf);
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#ifndef HVA_MEM_H
#define HVA_MEM_H
/**
* struct hva_buffer - hva buffer
*
* @name: name of requester
* @paddr: physical address (for hardware)
* @vaddr: virtual address (kernel can read/write)
* @size: size of buffer
*/
struct hva_buffer {
const char *name;
dma_addr_t paddr;
void *vaddr;
u32 size;
};
int hva_mem_alloc(struct hva_ctx *ctx,
__u32 size,
const char *name,
struct hva_buffer **buf);
void hva_mem_free(struct hva_ctx *ctx,
struct hva_buffer *buf);
#endif /* HVA_MEM_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,407 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) STMicroelectronics SA 2015
* Authors: Yannick Fertre <yannick.fertre@st.com>
* Hugues Fruchet <hugues.fruchet@st.com>
*/
#ifndef HVA_H
#define HVA_H
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-mem2mem.h>
#define hva_to_dev(h) (h->dev)
#define ctx_to_dev(c) (c->hva_dev->dev)
#define ctx_to_hdev(c) (c->hva_dev)
#define HVA_NAME "st-hva"
#define HVA_PREFIX "[---:----]"
extern const struct hva_enc nv12h264enc;
extern const struct hva_enc nv21h264enc;
/**
* struct hva_frameinfo - information about hva frame
*
* @pixelformat: fourcc code for uncompressed video format
* @width: width of frame
* @height: height of frame
* @aligned_width: width of frame (with encoder alignment constraint)
* @aligned_height: height of frame (with encoder alignment constraint)
* @size: maximum size in bytes required for data
*/
struct hva_frameinfo {
u32 pixelformat;
u32 width;
u32 height;
u32 aligned_width;
u32 aligned_height;
u32 size;
};
/**
* struct hva_streaminfo - information about hva stream
*
* @streamformat: fourcc code of compressed video format (H.264...)
* @width: width of stream
* @height: height of stream
* @profile: profile string
* @level: level string
*/
struct hva_streaminfo {
u32 streamformat;
u32 width;
u32 height;
u8 profile[32];
u8 level[32];
};
/**
* struct hva_controls - hva controls set
*
* @time_per_frame: time per frame in seconds
* @bitrate_mode: bitrate mode (constant bitrate or variable bitrate)
* @gop_size: groupe of picture size
* @bitrate: bitrate (in bps)
* @aspect: video aspect
* @profile: H.264 profile
* @level: H.264 level
* @entropy_mode: H.264 entropy mode (CABAC or CVLC)
* @cpb_size: coded picture buffer size (in kB)
* @dct8x8: transform mode 8x8 enable
* @qpmin: minimum quantizer
* @qpmax: maximum quantizer
* @vui_sar: pixel aspect ratio enable
* @vui_sar_idc: pixel aspect ratio identifier
* @sei_fp: sei frame packing arrangement enable
* @sei_fp_type: sei frame packing arrangement type
*/
struct hva_controls {
struct v4l2_fract time_per_frame;
enum v4l2_mpeg_video_bitrate_mode bitrate_mode;
u32 gop_size;
u32 bitrate;
enum v4l2_mpeg_video_aspect aspect;
enum v4l2_mpeg_video_h264_profile profile;
enum v4l2_mpeg_video_h264_level level;
enum v4l2_mpeg_video_h264_entropy_mode entropy_mode;
u32 cpb_size;
bool dct8x8;
u32 qpmin;
u32 qpmax;
bool vui_sar;
enum v4l2_mpeg_video_h264_vui_sar_idc vui_sar_idc;
bool sei_fp;
enum v4l2_mpeg_video_h264_sei_fp_arrangement_type sei_fp_type;
};
/**
* struct hva_frame - hva frame buffer (output)
*
* @vbuf: video buffer information for V4L2
* @list: V4L2 m2m list that the frame belongs to
* @info: frame information (width, height, format, alignment...)
* @paddr: physical address (for hardware)
* @vaddr: virtual address (kernel can read/write)
* @prepared: true if vaddr/paddr are resolved
*/
struct hva_frame {
struct vb2_v4l2_buffer vbuf;
struct list_head list;
struct hva_frameinfo info;
dma_addr_t paddr;
void *vaddr;
bool prepared;
};
/*
* to_hva_frame() - cast struct vb2_v4l2_buffer * to struct hva_frame *
*/
#define to_hva_frame(vb) \
container_of(vb, struct hva_frame, vbuf)
/**
* struct hva_stream - hva stream buffer (capture)
*
* @vbuf: video buffer information for V4L2
* @list: V4L2 m2m list that the frame belongs to
* @paddr: physical address (for hardware)
* @vaddr: virtual address (kernel can read/write)
* @prepared: true if vaddr/paddr are resolved
* @size: size of the buffer in bytes
* @bytesused: number of bytes occupied by data in the buffer
*/
struct hva_stream {
struct vb2_v4l2_buffer vbuf;
struct list_head list;
dma_addr_t paddr;
void *vaddr;
bool prepared;
unsigned int size;
unsigned int bytesused;
};
/*
* to_hva_stream() - cast struct vb2_v4l2_buffer * to struct hva_stream *
*/
#define to_hva_stream(vb) \
container_of(vb, struct hva_stream, vbuf)
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
/**
* struct hva_ctx_dbg - instance context debug info
*
* @debugfs_entry: debugfs entry
* @is_valid_period: true if the sequence is valid for performance
* @begin: start time of last HW task
* @total_duration: total HW processing durations in 0.1ms
* @cnt_duration: number of HW processings
* @min_duration: minimum HW processing duration in 0.1ms
* @max_duration: maximum HW processing duration in 0.1ms
* @avg_duration: average HW processing duration in 0.1ms
* @max_fps: maximum frames encoded per second (in 0.1Hz)
* @total_period: total encoding periods in 0.1ms
* @cnt_period: number of periods
* @min_period: minimum encoding period in 0.1ms
* @max_period: maximum encoding period in 0.1ms
* @avg_period: average encoding period in 0.1ms
* @total_stream_size: total number of encoded bytes
* @avg_fps: average frames encoded per second (in 0.1Hz)
* @window_duration: duration of the sampling window in 0.1ms
* @cnt_window: number of samples in the window
* @window_stream_size: number of encoded bytes upon the sampling window
* @last_bitrate: bitrate upon the last sampling window
* @min_bitrate: minimum bitrate in kbps
* @max_bitrate: maximum bitrate in kbps
* @avg_bitrate: average bitrate in kbps
*/
struct hva_ctx_dbg {
struct dentry *debugfs_entry;
bool is_valid_period;
ktime_t begin;
u32 total_duration;
u32 cnt_duration;
u32 min_duration;
u32 max_duration;
u32 avg_duration;
u32 max_fps;
u32 total_period;
u32 cnt_period;
u32 min_period;
u32 max_period;
u32 avg_period;
u32 total_stream_size;
u32 avg_fps;
u32 window_duration;
u32 cnt_window;
u32 window_stream_size;
u32 last_bitrate;
u32 min_bitrate;
u32 max_bitrate;
u32 avg_bitrate;
};
#endif
struct hva_dev;
struct hva_enc;
/**
* struct hva_ctx - context of hva instance
*
* @hva_dev: the device that this instance is associated with
* @fh: V4L2 file handle
* @ctrl_handler: V4L2 controls handler
* @ctrls: hva controls set
* @id: instance identifier
* @aborting: true if current job aborted
* @name: instance name (debug purpose)
* @run_work: encode work
* @lock: mutex used to lock access of this context
* @flags: validity of streaminfo and frameinfo fields
* @frame_num: frame number
* @stream_num: stream number
* @max_stream_size: maximum size in bytes required for stream data
* @colorspace: colorspace identifier
* @xfer_func: transfer function identifier
* @ycbcr_enc: Y'CbCr encoding identifier
* @quantization: quantization identifier
* @streaminfo: stream properties
* @frameinfo: frame properties
* @enc: current encoder
* @priv: private codec data for this instance, allocated
* by encoder @open time
* @hw_err: true if hardware error detected
* @encoded_frames: number of encoded frames
* @sys_errors: number of system errors (memory, resource, pm...)
* @encode_errors: number of encoding errors (hw/driver errors)
* @frame_errors: number of frame errors (format, size, header...)
* @dbg: context debug info
*/
struct hva_ctx {
struct hva_dev *hva_dev;
struct v4l2_fh fh;
struct v4l2_ctrl_handler ctrl_handler;
struct hva_controls ctrls;
u8 id;
bool aborting;
char name[100];
struct work_struct run_work;
/* mutex protecting this data structure */
struct mutex lock;
u32 flags;
u32 frame_num;
u32 stream_num;
u32 max_stream_size;
enum v4l2_colorspace colorspace;
enum v4l2_xfer_func xfer_func;
enum v4l2_ycbcr_encoding ycbcr_enc;
enum v4l2_quantization quantization;
struct hva_streaminfo streaminfo;
struct hva_frameinfo frameinfo;
struct hva_enc *enc;
void *priv;
bool hw_err;
u32 encoded_frames;
u32 sys_errors;
u32 encode_errors;
u32 frame_errors;
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
struct hva_ctx_dbg dbg;
#endif
};
#define HVA_FLAG_STREAMINFO 0x0001
#define HVA_FLAG_FRAMEINFO 0x0002
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
/**
* struct hva_dev_dbg - device debug info
*
* @debugfs_entry: debugfs entry
* @last_ctx: debug information about last running instance context
*/
struct hva_dev_dbg {
struct dentry *debugfs_entry;
struct hva_ctx last_ctx;
};
#endif
#define HVA_MAX_INSTANCES 16
#define HVA_MAX_ENCODERS 10
#define HVA_MAX_FORMATS HVA_MAX_ENCODERS
/**
* struct hva_dev - abstraction for hva entity
*
* @v4l2_dev: V4L2 device
* @vdev: video device
* @pdev: platform device
* @dev: device
* @lock: mutex used for critical sections & V4L2 ops
* serialization
* @m2m_dev: memory-to-memory V4L2 device information
* @instances: opened instances
* @nb_of_instances: number of opened instances
* @instance_id: rolling counter identifying an instance (debug purpose)
* @regs: register io memory access
* @esram_addr: esram address
* @esram_size: esram size
* @clk: hva clock
* @irq_its: status interruption
* @irq_err: error interruption
* @work_queue: work queue to handle the encode jobs
* @protect_mutex: mutex used to lock access of hardware
* @interrupt: completion interrupt
* @ip_version: IP hardware version
* @encoders: registered encoders
* @nb_of_encoders: number of registered encoders
* @pixelformats: supported uncompressed video formats
* @nb_of_pixelformats: number of supported umcompressed video formats
* @streamformats: supported compressed video formats
* @nb_of_streamformats: number of supported compressed video formats
* @sfl_reg: status fifo level register value
* @sts_reg: status register value
* @lmi_err_reg: local memory interface error register value
* @emi_err_reg: external memory interface error register value
* @hec_mif_err_reg: HEC memory interface error register value
* @dbg: device debug info
*/
struct hva_dev {
struct v4l2_device v4l2_dev;
struct video_device *vdev;
struct platform_device *pdev;
struct device *dev;
/* mutex protecting vb2_queue structure */
struct mutex lock;
struct v4l2_m2m_dev *m2m_dev;
struct hva_ctx *instances[HVA_MAX_INSTANCES];
unsigned int nb_of_instances;
unsigned int instance_id;
void __iomem *regs;
u32 esram_addr;
u32 esram_size;
struct clk *clk;
int irq_its;
int irq_err;
struct workqueue_struct *work_queue;
/* mutex protecting hardware access */
struct mutex protect_mutex;
struct completion interrupt;
unsigned long int ip_version;
const struct hva_enc *encoders[HVA_MAX_ENCODERS];
u32 nb_of_encoders;
u32 pixelformats[HVA_MAX_FORMATS];
u32 nb_of_pixelformats;
u32 streamformats[HVA_MAX_FORMATS];
u32 nb_of_streamformats;
u32 sfl_reg;
u32 sts_reg;
u32 lmi_err_reg;
u32 emi_err_reg;
u32 hec_mif_err_reg;
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
struct hva_dev_dbg dbg;
#endif
};
/**
* struct hva_enc - hva encoder
*
* @name: encoder name
* @streamformat: fourcc code for compressed video format (H.264...)
* @pixelformat: fourcc code for uncompressed video format
* @max_width: maximum width of frame for this encoder
* @max_height: maximum height of frame for this encoder
* @open: open encoder
* @close: close encoder
* @encode: encode a frame (struct hva_frame) in a stream
* (struct hva_stream)
*/
struct hva_enc {
const char *name;
u32 streamformat;
u32 pixelformat;
u32 max_width;
u32 max_height;
int (*open)(struct hva_ctx *ctx);
int (*close)(struct hva_ctx *ctx);
int (*encode)(struct hva_ctx *ctx, struct hva_frame *frame,
struct hva_stream *stream);
};
#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
void hva_debugfs_create(struct hva_dev *hva);
void hva_debugfs_remove(struct hva_dev *hva);
void hva_dbg_ctx_create(struct hva_ctx *ctx);
void hva_dbg_ctx_remove(struct hva_ctx *ctx);
void hva_dbg_perf_begin(struct hva_ctx *ctx);
void hva_dbg_perf_end(struct hva_ctx *ctx, struct hva_stream *stream);
#endif
#endif /* HVA_H */
@@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-2.0-only
# V4L drivers
config VIDEO_STM32_CSI
tristate "STM32 Camera Serial Interface (CSI) support"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV && OF
depends on ARCH_STM32 || COMPILE_TEST
select MEDIA_CONTROLLER
select V4L2_FWNODE
help
This module makes the STM32 Camera Serial Interface (CSI)
available as a v4l2 device.
To compile this driver as a module, choose M here: the module
will be called stm32-csi.
config VIDEO_STM32_DCMI
tristate "STM32 Digital Camera Memory Interface (DCMI) support"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV && OF
depends on ARCH_STM32 || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select MEDIA_CONTROLLER
select V4L2_FWNODE
help
This module makes the STM32 Digital Camera Memory Interface (DCMI)
available as a v4l2 device.
To compile this driver as a module, choose M here: the module
will be called stm32-dcmi.
config VIDEO_STM32_DCMIPP
tristate "STM32 Digital Camera Memory Interface Pixel Processor (DCMIPP) support"
depends on V4L_PLATFORM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_STM32 || COMPILE_TEST
select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_CONTIG
select VIDEO_V4L2_SUBDEV_API
select V4L2_FWNODE
help
This module makes the STM32 Digital Camera Memory Interface
Pixel Processor (DCMIPP) available as a v4l2 device.
To compile this driver as a module, choose M here: the module
will be called stm32-dcmipp.
# Mem2mem drivers
config VIDEO_STM32_DMA2D
tristate "STM32 Chrom-Art Accelerator (DMA2D)"
depends on V4L_MEM2MEM_DRIVERS
depends on VIDEO_DEV
depends on ARCH_STM32 || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select V4L2_MEM2MEM_DEV
help
Enables DMA2D hardware support on stm32.
The STM32 DMA2D is a memory-to-memory engine for pixel conversion
and specialized DMA dedicated to image manipulation.
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VIDEO_STM32_CSI) += stm32-csi.o
obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o
obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp/
stm32-dma2d-objs := dma2d/dma2d.o dma2d/dma2d-hw.o
obj-$(CONFIG_VIDEO_STM32_DMA2D) += stm32-dma2d.o
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ST stm32 Chrom-Art - 2D Graphics Accelerator Driver
*
* Copyright (c) 2021 Dillon Min
* Dillon Min, <dillon.minfei@gmail.com>
*
* based on s5p-g2d
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Kamil Debski, <k.debski@samsung.com>
*/
#include <linux/io.h>
#include "dma2d.h"
#include "dma2d-regs.h"
static inline u32 reg_read(void __iomem *base, u32 reg)
{
return readl_relaxed(base + reg);
}
static inline void reg_write(void __iomem *base, u32 reg, u32 val)
{
writel_relaxed(val, base + reg);
}
static inline void reg_update_bits(void __iomem *base, u32 reg, u32 mask,
u32 val)
{
reg_write(base, reg, (reg_read(base, reg) & ~mask) | val);
}
void dma2d_start(struct dma2d_dev *d)
{
reg_update_bits(d->regs, DMA2D_CR_REG, CR_START, CR_START);
}
u32 dma2d_get_int(struct dma2d_dev *d)
{
return reg_read(d->regs, DMA2D_ISR_REG);
}
void dma2d_clear_int(struct dma2d_dev *d)
{
u32 isr_val = reg_read(d->regs, DMA2D_ISR_REG);
reg_write(d->regs, DMA2D_IFCR_REG, isr_val & 0x003f);
}
void dma2d_config_common(struct dma2d_dev *d, enum dma2d_op_mode op_mode,
u16 width, u16 height)
{
reg_update_bits(d->regs, DMA2D_CR_REG, CR_MODE_MASK,
op_mode << CR_MODE_SHIFT);
reg_write(d->regs, DMA2D_NLR_REG, (width << 16) | height);
}
void dma2d_config_out(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t o_addr)
{
reg_update_bits(d->regs, DMA2D_CR_REG, CR_CEIE, CR_CEIE);
reg_update_bits(d->regs, DMA2D_CR_REG, CR_CTCIE, CR_CTCIE);
reg_update_bits(d->regs, DMA2D_CR_REG, CR_CAEIE, CR_CAEIE);
reg_update_bits(d->regs, DMA2D_CR_REG, CR_TCIE, CR_TCIE);
reg_update_bits(d->regs, DMA2D_CR_REG, CR_TEIE, CR_TEIE);
if (frm->fmt->cmode >= CM_MODE_ARGB8888 &&
frm->fmt->cmode <= CM_MODE_ARGB4444)
reg_update_bits(d->regs, DMA2D_OPFCCR_REG, OPFCCR_CM_MASK,
frm->fmt->cmode);
reg_write(d->regs, DMA2D_OMAR_REG, o_addr);
reg_write(d->regs, DMA2D_OCOLR_REG,
(frm->a_rgb[3] << 24) |
(frm->a_rgb[2] << 16) |
(frm->a_rgb[1] << 8) |
frm->a_rgb[0]);
reg_update_bits(d->regs, DMA2D_OOR_REG, OOR_LO_MASK,
frm->line_offset & 0x3fff);
}
void dma2d_config_fg(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t f_addr)
{
reg_write(d->regs, DMA2D_FGMAR_REG, f_addr);
reg_update_bits(d->regs, DMA2D_FGOR_REG, FGOR_LO_MASK,
frm->line_offset);
if (frm->fmt->cmode >= CM_MODE_ARGB8888 &&
frm->fmt->cmode <= CM_MODE_A4)
reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_CM_MASK,
frm->fmt->cmode);
reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_AM_MASK,
(frm->a_mode << 16) & 0x03);
reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_ALPHA_MASK,
frm->a_rgb[3] << 24);
reg_write(d->regs, DMA2D_FGCOLR_REG,
(frm->a_rgb[2] << 16) |
(frm->a_rgb[1] << 8) |
frm->a_rgb[0]);
}
void dma2d_config_bg(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t b_addr)
{
reg_write(d->regs, DMA2D_BGMAR_REG, b_addr);
reg_update_bits(d->regs, DMA2D_BGOR_REG, BGOR_LO_MASK,
frm->line_offset);
if (frm->fmt->cmode >= CM_MODE_ARGB8888 &&
frm->fmt->cmode <= CM_MODE_A4)
reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_CM_MASK,
frm->fmt->cmode);
reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_AM_MASK,
(frm->a_mode << 16) & 0x03);
reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_ALPHA_MASK,
frm->a_rgb[3] << 24);
reg_write(d->regs, DMA2D_BGCOLR_REG,
(frm->a_rgb[2] << 16) |
(frm->a_rgb[1] << 8) |
frm->a_rgb[0]);
}
@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ST stm32 Chrom-Art - 2D Graphics Accelerator Driver
*
* Copyright (c) 2021 Dillon Min
* Dillon Min, <dillon.minfei@gmail.com>
*
* based on s5p-g2d
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Kamil Debski, <k.debski@samsung.com>
*/
#ifndef __DMA2D_REGS_H__
#define __DMA2D_REGS_H__
#define DMA2D_CR_REG 0x0000
#define CR_MODE_MASK GENMASK(17, 16)
#define CR_MODE_SHIFT 16
#define CR_M2M 0x0000
#define CR_M2M_PFC BIT(16)
#define CR_M2M_BLEND BIT(17)
#define CR_R2M (BIT(17) | BIT(16))
#define CR_CEIE BIT(13)
#define CR_CTCIE BIT(12)
#define CR_CAEIE BIT(11)
#define CR_TWIE BIT(10)
#define CR_TCIE BIT(9)
#define CR_TEIE BIT(8)
#define CR_ABORT BIT(2)
#define CR_SUSP BIT(1)
#define CR_START BIT(0)
#define DMA2D_ISR_REG 0x0004
#define ISR_CEIF BIT(5)
#define ISR_CTCIF BIT(4)
#define ISR_CAEIF BIT(3)
#define ISR_TWIF BIT(2)
#define ISR_TCIF BIT(1)
#define ISR_TEIF BIT(0)
#define DMA2D_IFCR_REG 0x0008
#define IFCR_CCEIF BIT(5)
#define IFCR_CCTCIF BIT(4)
#define IFCR_CAECIF BIT(3)
#define IFCR_CTWIF BIT(2)
#define IFCR_CTCIF BIT(1)
#define IFCR_CTEIF BIT(0)
#define DMA2D_FGMAR_REG 0x000c
#define DMA2D_FGOR_REG 0x0010
#define FGOR_LO_MASK GENMASK(13, 0)
#define DMA2D_BGMAR_REG 0x0014
#define DMA2D_BGOR_REG 0x0018
#define BGOR_LO_MASK GENMASK(13, 0)
#define DMA2D_FGPFCCR_REG 0x001c
#define FGPFCCR_ALPHA_MASK GENMASK(31, 24)
#define FGPFCCR_AM_MASK GENMASK(17, 16)
#define FGPFCCR_CS_MASK GENMASK(15, 8)
#define FGPFCCR_START BIT(5)
#define FGPFCCR_CCM_RGB888 BIT(4)
#define FGPFCCR_CM_MASK GENMASK(3, 0)
#define DMA2D_FGCOLR_REG 0x0020
#define FGCOLR_REG_MASK GENMASK(23, 16)
#define FGCOLR_GREEN_MASK GENMASK(15, 8)
#define FGCOLR_BLUE_MASK GENMASK(7, 0)
#define DMA2D_BGPFCCR_REG 0x0024
#define BGPFCCR_ALPHA_MASK GENMASK(31, 24)
#define BGPFCCR_AM_MASK GENMASK(17, 16)
#define BGPFCCR_CS_MASK GENMASK(15, 8)
#define BGPFCCR_START BIT(5)
#define BGPFCCR_CCM_RGB888 BIT(4)
#define BGPFCCR_CM_MASK GENMASK(3, 0)
#define DMA2D_BGCOLR_REG 0x0028
#define BGCOLR_REG_MASK GENMASK(23, 16)
#define BGCOLR_GREEN_MASK GENMASK(15, 8)
#define BGCOLR_BLUE_MASK GENMASK(7, 0)
#define DMA2D_OPFCCR_REG 0x0034
#define OPFCCR_CM_MASK GENMASK(2, 0)
#define DMA2D_OCOLR_REG 0x0038
#define OCOLR_ALPHA_MASK GENMASK(31, 24)
#define OCOLR_RED_MASK GENMASK(23, 16)
#define OCOLR_GREEN_MASK GENMASK(15, 8)
#define OCOLR_BLUE_MASK GENMASK(7, 0)
#define DMA2D_OMAR_REG 0x003c
#define DMA2D_OOR_REG 0x0040
#define OOR_LO_MASK GENMASK(13, 0)
#define DMA2D_NLR_REG 0x0044
#define NLR_PL_MASK GENMASK(29, 16)
#define NLR_NL_MASK GENMASK(15, 0)
/* Hardware limits */
#define MAX_WIDTH 2592
#define MAX_HEIGHT 2592
#define DEFAULT_WIDTH 240
#define DEFAULT_HEIGHT 320
#define DEFAULT_SIZE 307200
#define CM_MODE_ARGB8888 0x00
#define CM_MODE_ARGB4444 0x04
#define CM_MODE_A4 0x0a
#endif /* __DMA2D_REGS_H__ */
@@ -0,0 +1,727 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* STM32 DMA2D - 2D Graphics Accelerator Driver
*
* Copyright (c) 2021 Dillon Min
* Dillon Min, <dillon.minfei@gmail.com>
*
* based on s5p-g2d
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Kamil Debski, <k.debski@samsung.com>
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <media/v4l2-mem2mem.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-contig.h>
#include "dma2d.h"
#include "dma2d-regs.h"
/*
* This V4L2 subdev m2m driver enables Chrom-Art Accelerator unit
* of STMicroelectronics STM32 SoC series.
*
* Currently support r2m, m2m, m2m_pfc.
*
* - r2m, Filling a part or the whole of a destination image with a specific
* color.
* - m2m, Copying a part or the whole of a source image into a part or the
* whole of a destination.
* - m2m_pfc, Copying a part or the whole of a source image into a part or the
* whole of a destination image with a pixel format conversion.
*/
static inline struct dma2d_ctx *file2ctx(struct file *filp)
{
return container_of(file_to_v4l2_fh(filp), struct dma2d_ctx, fh);
}
static const struct dma2d_fmt formats[] = {
{
.fourcc = V4L2_PIX_FMT_ARGB32,
.cmode = DMA2D_CMODE_ARGB8888,
.depth = 32,
},
{
.fourcc = V4L2_PIX_FMT_RGB24,
.cmode = DMA2D_CMODE_RGB888,
.depth = 24,
},
{
.fourcc = V4L2_PIX_FMT_RGB565,
.cmode = DMA2D_CMODE_RGB565,
.depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_ARGB555,
.cmode = DMA2D_CMODE_ARGB1555,
.depth = 16,
},
{
.fourcc = V4L2_PIX_FMT_ARGB444,
.cmode = DMA2D_CMODE_ARGB4444,
.depth = 16,
},
};
#define NUM_FORMATS ARRAY_SIZE(formats)
static const struct dma2d_frame def_frame = {
.width = DEFAULT_WIDTH,
.height = DEFAULT_HEIGHT,
.line_offset = 0,
.a_rgb = {0x00, 0x00, 0x00, 0xff},
.a_mode = DMA2D_ALPHA_MODE_NO_MODIF,
.fmt = (struct dma2d_fmt *)&formats[0],
.size = DEFAULT_SIZE,
};
static struct dma2d_fmt *find_fmt(int pixelformat)
{
unsigned int i;
for (i = 0; i < NUM_FORMATS; i++) {
if (formats[i].fourcc == pixelformat)
return (struct dma2d_fmt *)&formats[i];
}
return NULL;
}
static struct dma2d_frame *get_frame(struct dma2d_ctx *ctx,
enum v4l2_buf_type type)
{
return V4L2_TYPE_IS_OUTPUT(type) ? &ctx->cap : &ctx->out;
}
static int dma2d_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], struct device *alloc_devs[])
{
struct dma2d_ctx *ctx = vb2_get_drv_priv(vq);
struct dma2d_frame *f = get_frame(ctx, vq->type);
if (*nplanes)
return sizes[0] < f->size ? -EINVAL : 0;
sizes[0] = f->size;
*nplanes = 1;
return 0;
}
static int dma2d_buf_out_validate(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
if (vbuf->field == V4L2_FIELD_ANY)
vbuf->field = V4L2_FIELD_NONE;
if (vbuf->field != V4L2_FIELD_NONE)
return -EINVAL;
return 0;
}
static int dma2d_buf_prepare(struct vb2_buffer *vb)
{
struct dma2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct dma2d_frame *f = get_frame(ctx, vb->vb2_queue->type);
if (vb2_plane_size(vb, 0) < f->size)
return -EINVAL;
vb2_set_plane_payload(vb, 0, f->size);
return 0;
}
static void dma2d_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct dma2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
}
static int dma2d_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct dma2d_ctx *ctx = vb2_get_drv_priv(q);
struct dma2d_frame *f = get_frame(ctx, q->type);
f->sequence = 0;
return 0;
}
static void dma2d_stop_streaming(struct vb2_queue *q)
{
struct dma2d_ctx *ctx = vb2_get_drv_priv(q);
struct vb2_v4l2_buffer *vbuf;
for (;;) {
if (V4L2_TYPE_IS_OUTPUT(q->type))
vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
else
vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
if (!vbuf)
return;
v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
}
}
static const struct vb2_ops dma2d_qops = {
.queue_setup = dma2d_queue_setup,
.buf_out_validate = dma2d_buf_out_validate,
.buf_prepare = dma2d_buf_prepare,
.buf_queue = dma2d_buf_queue,
.start_streaming = dma2d_start_streaming,
.stop_streaming = dma2d_stop_streaming,
};
static int queue_init(void *priv, struct vb2_queue *src_vq,
struct vb2_queue *dst_vq)
{
struct dma2d_ctx *ctx = priv;
int ret;
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
src_vq->drv_priv = ctx;
src_vq->ops = &dma2d_qops;
src_vq->mem_ops = &vb2_dma_contig_memops;
src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
src_vq->lock = &ctx->dev->mutex;
src_vq->dev = ctx->dev->v4l2_dev.dev;
ret = vb2_queue_init(src_vq);
if (ret)
return ret;
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
dst_vq->drv_priv = ctx;
dst_vq->ops = &dma2d_qops;
dst_vq->mem_ops = &vb2_dma_contig_memops;
dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
dst_vq->lock = &ctx->dev->mutex;
dst_vq->dev = ctx->dev->v4l2_dev.dev;
return vb2_queue_init(dst_vq);
}
static int dma2d_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct dma2d_frame *frm;
struct dma2d_ctx *ctx = container_of(ctrl->handler, struct dma2d_ctx,
ctrl_handler);
unsigned long flags;
spin_lock_irqsave(&ctx->dev->ctrl_lock, flags);
switch (ctrl->id) {
case V4L2_CID_COLORFX:
if (ctrl->val == V4L2_COLORFX_SET_RGB)
ctx->op_mode = DMA2D_MODE_R2M;
else if (ctrl->val == V4L2_COLORFX_NONE)
ctx->op_mode = DMA2D_MODE_M2M;
break;
case V4L2_CID_COLORFX_RGB:
frm = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
frm->a_rgb[2] = (ctrl->val >> 16) & 0xff;
frm->a_rgb[1] = (ctrl->val >> 8) & 0xff;
frm->a_rgb[0] = (ctrl->val >> 0) & 0xff;
break;
default:
spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags);
return -EINVAL;
}
spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags);
return 0;
}
static const struct v4l2_ctrl_ops dma2d_ctrl_ops = {
.s_ctrl = dma2d_s_ctrl,
};
static int dma2d_setup_ctrls(struct dma2d_ctx *ctx)
{
struct v4l2_ctrl_handler *handler = &ctx->ctrl_handler;
v4l2_ctrl_handler_init(handler, 2);
v4l2_ctrl_new_std_menu(handler, &dma2d_ctrl_ops, V4L2_CID_COLORFX,
V4L2_COLORFX_SET_RGB, ~0x10001,
V4L2_COLORFX_NONE);
v4l2_ctrl_new_std(handler, &dma2d_ctrl_ops, V4L2_CID_COLORFX_RGB, 0,
0xffffff, 1, 0);
return 0;
}
static int dma2d_open(struct file *file)
{
struct dma2d_dev *dev = video_drvdata(file);
struct dma2d_ctx *ctx = NULL;
int ret = 0;
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
ctx->dev = dev;
/* Set default formats */
ctx->cap = def_frame;
ctx->bg = def_frame;
ctx->out = def_frame;
ctx->op_mode = DMA2D_MODE_M2M_FPC;
ctx->colorspace = V4L2_COLORSPACE_REC709;
if (mutex_lock_interruptible(&dev->mutex)) {
kfree(ctx);
return -ERESTARTSYS;
}
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init);
if (IS_ERR(ctx->fh.m2m_ctx)) {
ret = PTR_ERR(ctx->fh.m2m_ctx);
mutex_unlock(&dev->mutex);
kfree(ctx);
return ret;
}
v4l2_fh_init(&ctx->fh, video_devdata(file));
v4l2_fh_add(&ctx->fh, file);
dma2d_setup_ctrls(ctx);
/* Write the default values to the ctx struct */
v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
ctx->fh.ctrl_handler = &ctx->ctrl_handler;
mutex_unlock(&dev->mutex);
return 0;
}
static int dma2d_release(struct file *file)
{
struct dma2d_dev *dev = video_drvdata(file);
struct dma2d_ctx *ctx = file2ctx(file);
mutex_lock(&dev->mutex);
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
mutex_unlock(&dev->mutex);
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh, file);
v4l2_fh_exit(&ctx->fh);
kfree(ctx);
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
strscpy(cap->driver, DMA2D_NAME, sizeof(cap->driver));
strscpy(cap->card, DMA2D_NAME, sizeof(cap->card));
strscpy(cap->bus_info, BUS_INFO, sizeof(cap->bus_info));
return 0;
}
static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
if (f->index >= NUM_FORMATS)
return -EINVAL;
f->pixelformat = formats[f->index].fourcc;
return 0;
}
static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct dma2d_ctx *ctx = file2ctx(file);
struct dma2d_frame *frm;
frm = get_frame(ctx, f->type);
f->fmt.pix.width = frm->width;
f->fmt.pix.height = frm->height;
f->fmt.pix.field = V4L2_FIELD_NONE;
f->fmt.pix.pixelformat = frm->fmt->fourcc;
f->fmt.pix.bytesperline = (frm->width * frm->fmt->depth) >> 3;
f->fmt.pix.sizeimage = frm->size;
f->fmt.pix.colorspace = ctx->colorspace;
f->fmt.pix.xfer_func = ctx->xfer_func;
f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc;
f->fmt.pix.quantization = ctx->quant;
return 0;
}
static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct dma2d_ctx *ctx = file2ctx(file);
struct dma2d_fmt *fmt;
enum v4l2_field *field;
u32 fourcc = f->fmt.pix.pixelformat;
fmt = find_fmt(fourcc);
if (!fmt) {
f->fmt.pix.pixelformat = formats[0].fourcc;
fmt = find_fmt(f->fmt.pix.pixelformat);
}
field = &f->fmt.pix.field;
if (*field == V4L2_FIELD_ANY)
*field = V4L2_FIELD_NONE;
else if (*field != V4L2_FIELD_NONE)
return -EINVAL;
if (f->fmt.pix.width > MAX_WIDTH)
f->fmt.pix.width = MAX_WIDTH;
if (f->fmt.pix.height > MAX_HEIGHT)
f->fmt.pix.height = MAX_HEIGHT;
if (f->fmt.pix.width < 1)
f->fmt.pix.width = 1;
if (f->fmt.pix.height < 1)
f->fmt.pix.height = 1;
if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && !f->fmt.pix.colorspace) {
f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
} else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
f->fmt.pix.colorspace = ctx->colorspace;
f->fmt.pix.xfer_func = ctx->xfer_func;
f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc;
f->fmt.pix.quantization = ctx->quant;
}
f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct dma2d_ctx *ctx = file2ctx(file);
struct vb2_queue *vq;
struct dma2d_frame *frm;
struct dma2d_fmt *fmt;
int ret = 0;
/* Adjust all values accordingly to the hardware capabilities
* and chosen format.
*/
ret = vidioc_try_fmt(file, priv, f);
if (ret)
return ret;
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
if (vb2_is_busy(vq))
return -EBUSY;
fmt = find_fmt(f->fmt.pix.pixelformat);
if (!fmt)
return -EINVAL;
if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
ctx->colorspace = f->fmt.pix.colorspace;
ctx->xfer_func = f->fmt.pix.xfer_func;
ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc;
ctx->quant = f->fmt.pix.quantization;
}
frm = get_frame(ctx, f->type);
frm->width = f->fmt.pix.width;
frm->height = f->fmt.pix.height;
frm->size = f->fmt.pix.sizeimage;
/* Reset crop settings */
frm->o_width = 0;
frm->o_height = 0;
frm->c_width = frm->width;
frm->c_height = frm->height;
frm->right = frm->width;
frm->bottom = frm->height;
frm->fmt = fmt;
frm->line_offset = 0;
return 0;
}
static void device_run(void *prv)
{
struct dma2d_ctx *ctx = prv;
struct dma2d_dev *dev = ctx->dev;
struct dma2d_frame *frm_out, *frm_cap;
struct vb2_v4l2_buffer *src, *dst;
unsigned long flags;
spin_lock_irqsave(&dev->ctrl_lock, flags);
dev->curr = ctx;
src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
if (!dst || !src)
goto end;
frm_cap = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
frm_out = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT);
if (!frm_cap || !frm_out)
goto end;
src->sequence = frm_out->sequence++;
dst->sequence = frm_cap->sequence++;
v4l2_m2m_buf_copy_metadata(src, dst);
if (clk_enable(dev->gate))
goto end;
dma2d_config_fg(dev, frm_out,
vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0));
/* TODO: add M2M_BLEND handler here */
if (ctx->op_mode != DMA2D_MODE_R2M) {
if (frm_out->fmt->fourcc == frm_cap->fmt->fourcc)
ctx->op_mode = DMA2D_MODE_M2M;
else
ctx->op_mode = DMA2D_MODE_M2M_FPC;
}
dma2d_config_out(dev, frm_cap,
vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0));
dma2d_config_common(dev, ctx->op_mode, frm_cap->width, frm_cap->height);
dma2d_start(dev);
end:
spin_unlock_irqrestore(&dev->ctrl_lock, flags);
}
static irqreturn_t dma2d_isr(int irq, void *prv)
{
struct dma2d_dev *dev = prv;
struct dma2d_ctx *ctx = dev->curr;
struct vb2_v4l2_buffer *src, *dst;
u32 s = dma2d_get_int(dev);
dma2d_clear_int(dev);
if (s & ISR_TCIF || s == 0) {
clk_disable(dev->gate);
WARN_ON(!ctx);
src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
WARN_ON(!dst);
WARN_ON(!src);
v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
dev->curr = NULL;
}
return IRQ_HANDLED;
}
static const struct v4l2_file_operations dma2d_fops = {
.owner = THIS_MODULE,
.open = dma2d_open,
.release = dma2d_release,
.poll = v4l2_m2m_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = v4l2_m2m_fop_mmap,
#ifndef CONFIG_MMU
.get_unmapped_area = v4l2_m2m_get_unmapped_area,
#endif
};
static const struct v4l2_ioctl_ops dma2d_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt,
.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
.vidioc_g_fmt_vid_out = vidioc_g_fmt,
.vidioc_try_fmt_vid_out = vidioc_try_fmt,
.vidioc_s_fmt_vid_out = vidioc_s_fmt,
.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
.vidioc_streamon = v4l2_m2m_ioctl_streamon,
.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct video_device dma2d_videodev = {
.name = DMA2D_NAME,
.fops = &dma2d_fops,
.ioctl_ops = &dma2d_ioctl_ops,
.minor = -1,
.release = video_device_release,
.vfl_dir = VFL_DIR_M2M,
};
static const struct v4l2_m2m_ops dma2d_m2m_ops = {
.device_run = device_run,
};
static const struct of_device_id stm32_dma2d_match[];
static int dma2d_probe(struct platform_device *pdev)
{
struct dma2d_dev *dev;
struct video_device *vfd;
int ret = 0;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
spin_lock_init(&dev->ctrl_lock);
mutex_init(&dev->mutex);
atomic_set(&dev->num_inst, 0);
dev->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(dev->regs))
return PTR_ERR(dev->regs);
dev->gate = clk_get(&pdev->dev, "dma2d");
if (IS_ERR(dev->gate)) {
dev_err(&pdev->dev, "failed to get dma2d clock gate\n");
ret = -ENXIO;
return ret;
}
ret = clk_prepare(dev->gate);
if (ret) {
dev_err(&pdev->dev, "failed to prepare dma2d clock gate\n");
goto put_clk_gate;
}
ret = platform_get_irq(pdev, 0);
if (ret < 0)
goto unprep_clk_gate;
dev->irq = ret;
ret = devm_request_irq(&pdev->dev, dev->irq, dma2d_isr,
0, pdev->name, dev);
if (ret) {
dev_err(&pdev->dev, "failed to install IRQ\n");
goto unprep_clk_gate;
}
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret)
goto unprep_clk_gate;
vfd = video_device_alloc();
if (!vfd) {
v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
ret = -ENOMEM;
goto unreg_v4l2_dev;
}
*vfd = dma2d_videodev;
vfd->lock = &dev->mutex;
vfd->v4l2_dev = &dev->v4l2_dev;
vfd->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING;
platform_set_drvdata(pdev, dev);
dev->m2m_dev = v4l2_m2m_init(&dma2d_m2m_ops);
if (IS_ERR(dev->m2m_dev)) {
v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
ret = PTR_ERR(dev->m2m_dev);
goto rel_vdev;
}
ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0);
if (ret) {
v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
goto free_m2m;
}
video_set_drvdata(vfd, dev);
dev->vfd = vfd;
v4l2_info(&dev->v4l2_dev, "device registered as /dev/video%d\n",
vfd->num);
return 0;
free_m2m:
v4l2_m2m_release(dev->m2m_dev);
rel_vdev:
video_device_release(vfd);
unreg_v4l2_dev:
v4l2_device_unregister(&dev->v4l2_dev);
unprep_clk_gate:
clk_unprepare(dev->gate);
put_clk_gate:
clk_put(dev->gate);
return ret;
}
static void dma2d_remove(struct platform_device *pdev)
{
struct dma2d_dev *dev = platform_get_drvdata(pdev);
v4l2_info(&dev->v4l2_dev, "Removing " DMA2D_NAME);
v4l2_m2m_release(dev->m2m_dev);
video_unregister_device(dev->vfd);
v4l2_device_unregister(&dev->v4l2_dev);
vb2_dma_contig_clear_max_seg_size(&pdev->dev);
clk_unprepare(dev->gate);
clk_put(dev->gate);
}
static const struct of_device_id stm32_dma2d_match[] = {
{
.compatible = "st,stm32-dma2d",
.data = NULL,
},
{},
};
MODULE_DEVICE_TABLE(of, stm32_dma2d_match);
static struct platform_driver dma2d_pdrv = {
.probe = dma2d_probe,
.remove = dma2d_remove,
.driver = {
.name = DMA2D_NAME,
.of_match_table = stm32_dma2d_match,
},
};
module_platform_driver(dma2d_pdrv);
MODULE_AUTHOR("Dillon Min <dillon.minfei@gmail.com>");
MODULE_DESCRIPTION("STM32 Chrom-Art Accelerator DMA2D driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* ST stm32 DMA2D - 2D Graphics Accelerator Driver
*
* Copyright (c) 2021 Dillon Min
* Dillon Min, <dillon.minfei@gmail.com>
*
* based on s5p-g2d
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Kamil Debski, <k.debski@samsung.com>
*/
#ifndef __DMA2D_H__
#define __DMA2D_H__
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
#define DMA2D_NAME "stm-dma2d"
#define BUS_INFO "platform:stm-dma2d"
enum dma2d_op_mode {
DMA2D_MODE_M2M,
DMA2D_MODE_M2M_FPC,
DMA2D_MODE_M2M_BLEND,
DMA2D_MODE_R2M
};
enum dma2d_cmode {
/* output pfc cmode from ARGB888 to ARGB4444 */
DMA2D_CMODE_ARGB8888,
DMA2D_CMODE_RGB888,
DMA2D_CMODE_RGB565,
DMA2D_CMODE_ARGB1555,
DMA2D_CMODE_ARGB4444,
/* bg or fg pfc cmode from L8 to A4 */
DMA2D_CMODE_L8,
DMA2D_CMODE_AL44,
DMA2D_CMODE_AL88,
DMA2D_CMODE_L4,
DMA2D_CMODE_A8,
DMA2D_CMODE_A4
};
enum dma2d_alpha_mode {
DMA2D_ALPHA_MODE_NO_MODIF,
DMA2D_ALPHA_MODE_REPLACE,
DMA2D_ALPHA_MODE_COMBINE
};
struct dma2d_fmt {
u32 fourcc;
int depth;
enum dma2d_cmode cmode;
};
struct dma2d_frame {
/* Original dimensions */
u32 width;
u32 height;
/* Crop size */
u32 c_width;
u32 c_height;
/* Offset */
u32 o_width;
u32 o_height;
u32 bottom;
u32 right;
u16 line_offset;
/* Image format */
struct dma2d_fmt *fmt;
/* [0]: blue
* [1]: green
* [2]: red
* [3]: alpha
*/
u8 a_rgb[4];
/*
* AM[1:0] of DMA2D_FGPFCCR
*/
enum dma2d_alpha_mode a_mode;
u32 size;
unsigned int sequence;
};
struct dma2d_ctx {
struct v4l2_fh fh;
struct dma2d_dev *dev;
struct dma2d_frame cap;
struct dma2d_frame out;
struct dma2d_frame bg;
/*
* MODE[17:16] of DMA2D_CR
*/
enum dma2d_op_mode op_mode;
struct v4l2_ctrl_handler ctrl_handler;
enum v4l2_colorspace colorspace;
enum v4l2_ycbcr_encoding ycbcr_enc;
enum v4l2_xfer_func xfer_func;
enum v4l2_quantization quant;
};
struct dma2d_dev {
struct v4l2_device v4l2_dev;
struct v4l2_m2m_dev *m2m_dev;
struct video_device *vfd;
/* for device open/close etc */
struct mutex mutex;
/* to avoid the conflict with device running and user setting
* at the same time
*/
spinlock_t ctrl_lock;
atomic_t num_inst;
void __iomem *regs;
struct clk *gate;
struct dma2d_ctx *curr;
int irq;
};
void dma2d_start(struct dma2d_dev *d);
u32 dma2d_get_int(struct dma2d_dev *d);
void dma2d_clear_int(struct dma2d_dev *d);
void dma2d_config_out(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t o_addr);
void dma2d_config_fg(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t f_addr);
void dma2d_config_bg(struct dma2d_dev *d, struct dma2d_frame *frm,
dma_addr_t b_addr);
void dma2d_config_common(struct dma2d_dev *d, enum dma2d_op_mode op_mode,
u16 width, u16 height);
#endif /* __DMA2D_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
stm32-dcmipp-y := dcmipp-core.o dcmipp-common.o dcmipp-input.o dcmipp-byteproc.o dcmipp-bytecap.o
obj-$(CONFIG_VIDEO_STM32_DCMIPP) += stm32-dcmipp.o
@@ -0,0 +1,968 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#include <linux/iopoll.h>
#include <linux/pm_runtime.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
#include "dcmipp-common.h"
#define DCMIPP_PRSR 0x1f8
#define DCMIPP_CMIER 0x3f0
#define DCMIPP_CMIER_P0FRAMEIE BIT(9)
#define DCMIPP_CMIER_P0VSYNCIE BIT(10)
#define DCMIPP_CMIER_P0OVRIE BIT(15)
#define DCMIPP_CMIER_P0ALL (DCMIPP_CMIER_P0VSYNCIE |\
DCMIPP_CMIER_P0FRAMEIE |\
DCMIPP_CMIER_P0OVRIE)
#define DCMIPP_CMSR1 0x3f4
#define DCMIPP_CMSR2 0x3f8
#define DCMIPP_CMSR2_P0FRAMEF BIT(9)
#define DCMIPP_CMSR2_P0VSYNCF BIT(10)
#define DCMIPP_CMSR2_P0OVRF BIT(15)
#define DCMIPP_CMFCR 0x3fc
#define DCMIPP_P0FSCR 0x404
#define DCMIPP_P0FSCR_PIPEN BIT(31)
#define DCMIPP_P0FCTCR 0x500
#define DCMIPP_P0FCTCR_CPTREQ BIT(3)
#define DCMIPP_P0DCCNTR 0x5b0
#define DCMIPP_P0DCLMTR 0x5b4
#define DCMIPP_P0DCLMTR_ENABLE BIT(31)
#define DCMIPP_P0DCLMTR_LIMIT_MASK GENMASK(23, 0)
#define DCMIPP_P0PPM0AR1 0x5c4
#define DCMIPP_P0SR 0x5f8
#define DCMIPP_P0SR_CPTACT BIT(23)
struct dcmipp_bytecap_pix_map {
unsigned int code;
u32 pixelformat;
};
#define PIXMAP_MBUS_PFMT(mbus, fmt) \
{ \
.code = MEDIA_BUS_FMT_##mbus, \
.pixelformat = V4L2_PIX_FMT_##fmt \
}
static const struct dcmipp_bytecap_pix_map dcmipp_bytecap_pix_map_list[] = {
PIXMAP_MBUS_PFMT(RGB565_2X8_LE, RGB565),
PIXMAP_MBUS_PFMT(RGB565_1X16, RGB565),
PIXMAP_MBUS_PFMT(RGB888_1X24, RGB24),
PIXMAP_MBUS_PFMT(YUYV8_2X8, YUYV),
PIXMAP_MBUS_PFMT(YUYV8_1X16, YUYV),
PIXMAP_MBUS_PFMT(YVYU8_2X8, YVYU),
PIXMAP_MBUS_PFMT(YVYU8_1X16, YVYU),
PIXMAP_MBUS_PFMT(UYVY8_2X8, UYVY),
PIXMAP_MBUS_PFMT(UYVY8_1X16, UYVY),
PIXMAP_MBUS_PFMT(VYUY8_2X8, VYUY),
PIXMAP_MBUS_PFMT(VYUY8_1X16, VYUY),
PIXMAP_MBUS_PFMT(Y8_1X8, GREY),
PIXMAP_MBUS_PFMT(Y10_1X10, Y10),
PIXMAP_MBUS_PFMT(Y12_1X12, Y12),
PIXMAP_MBUS_PFMT(Y14_1X14, Y14),
PIXMAP_MBUS_PFMT(SBGGR8_1X8, SBGGR8),
PIXMAP_MBUS_PFMT(SGBRG8_1X8, SGBRG8),
PIXMAP_MBUS_PFMT(SGRBG8_1X8, SGRBG8),
PIXMAP_MBUS_PFMT(SRGGB8_1X8, SRGGB8),
PIXMAP_MBUS_PFMT(SBGGR10_1X10, SBGGR10),
PIXMAP_MBUS_PFMT(SGBRG10_1X10, SGBRG10),
PIXMAP_MBUS_PFMT(SGRBG10_1X10, SGRBG10),
PIXMAP_MBUS_PFMT(SRGGB10_1X10, SRGGB10),
PIXMAP_MBUS_PFMT(SBGGR12_1X12, SBGGR12),
PIXMAP_MBUS_PFMT(SGBRG12_1X12, SGBRG12),
PIXMAP_MBUS_PFMT(SGRBG12_1X12, SGRBG12),
PIXMAP_MBUS_PFMT(SRGGB12_1X12, SRGGB12),
PIXMAP_MBUS_PFMT(SBGGR14_1X14, SBGGR14),
PIXMAP_MBUS_PFMT(SGBRG14_1X14, SGBRG14),
PIXMAP_MBUS_PFMT(SGRBG14_1X14, SGRBG14),
PIXMAP_MBUS_PFMT(SRGGB14_1X14, SRGGB14),
PIXMAP_MBUS_PFMT(JPEG_1X8, JPEG),
};
static const struct dcmipp_bytecap_pix_map *
dcmipp_bytecap_pix_map_by_pixelformat(u32 pixelformat)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) {
if (dcmipp_bytecap_pix_map_list[i].pixelformat == pixelformat)
return &dcmipp_bytecap_pix_map_list[i];
}
return NULL;
}
struct dcmipp_buf {
struct vb2_v4l2_buffer vb;
bool prepared;
dma_addr_t addr;
size_t size;
struct list_head list;
};
enum dcmipp_state {
DCMIPP_STOPPED = 0,
DCMIPP_WAIT_FOR_BUFFER,
DCMIPP_RUNNING,
};
struct dcmipp_bytecap_device {
struct dcmipp_ent_device ved;
struct video_device vdev;
struct device *dev;
struct v4l2_pix_format format;
struct vb2_queue queue;
struct list_head buffers;
/*
* Protects concurrent calls of buf queue / irq handler
* and buffer handling related variables / lists
*/
spinlock_t irqlock;
/* mutex used as vdev and queue lock */
struct mutex lock;
u32 sequence;
struct media_pipeline pipe;
struct v4l2_subdev *s_subdev;
u32 s_subdev_pad_nb;
enum dcmipp_state state;
/*
* DCMIPP driver is handling 2 buffers
* active: buffer into which DCMIPP is currently writing into
* next: buffer given to the DCMIPP and which will become
* automatically active on next VSYNC
*/
struct dcmipp_buf *active, *next;
void __iomem *regs;
u32 cmsr2;
struct {
u32 errors;
u32 limit;
u32 overrun;
u32 buffers;
u32 vsync;
u32 frame;
u32 it;
u32 underrun;
u32 nactive;
} count;
};
static const struct v4l2_pix_format fmt_default = {
.width = DCMIPP_FMT_WIDTH_DEFAULT,
.height = DCMIPP_FMT_HEIGHT_DEFAULT,
.pixelformat = V4L2_PIX_FMT_RGB565,
.field = V4L2_FIELD_NONE,
.bytesperline = DCMIPP_FMT_WIDTH_DEFAULT * 2,
.sizeimage = DCMIPP_FMT_WIDTH_DEFAULT * DCMIPP_FMT_HEIGHT_DEFAULT * 2,
.colorspace = DCMIPP_COLORSPACE_DEFAULT,
.ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
.quantization = DCMIPP_QUANTIZATION_DEFAULT,
.xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
};
static int dcmipp_bytecap_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
strscpy(cap->driver, DCMIPP_PDEV_NAME, sizeof(cap->driver));
strscpy(cap->card, KBUILD_MODNAME, sizeof(cap->card));
return 0;
}
static int dcmipp_bytecap_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct dcmipp_bytecap_device *vcap = video_drvdata(file);
f->fmt.pix = vcap->format;
return 0;
}
static int dcmipp_bytecap_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct dcmipp_bytecap_device *vcap = video_drvdata(file);
struct v4l2_pix_format *format = &f->fmt.pix;
const struct dcmipp_bytecap_pix_map *vpix;
u32 in_w, in_h;
/* Don't accept a pixelformat that is not on the table */
vpix = dcmipp_bytecap_pix_map_by_pixelformat(format->pixelformat);
if (!vpix)
format->pixelformat = fmt_default.pixelformat;
/* Adjust width & height */
in_w = format->width;
in_h = format->height;
v4l_bound_align_image(&format->width, DCMIPP_FRAME_MIN_WIDTH,
DCMIPP_FRAME_MAX_WIDTH, 0, &format->height,
DCMIPP_FRAME_MIN_HEIGHT, DCMIPP_FRAME_MAX_HEIGHT,
0, 0);
if (format->width != in_w || format->height != in_h)
dev_dbg(vcap->dev, "resolution updated: %dx%d -> %dx%d\n",
in_w, in_h, format->width, format->height);
if (format->pixelformat == V4L2_PIX_FMT_JPEG) {
format->bytesperline = format->width;
format->sizeimage = format->bytesperline * format->height;
} else {
v4l2_fill_pixfmt(format, format->pixelformat,
format->width, format->height);
}
if (format->field == V4L2_FIELD_ANY)
format->field = fmt_default.field;
dcmipp_colorimetry_clamp(format);
return 0;
}
static int dcmipp_bytecap_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct dcmipp_bytecap_device *vcap = video_drvdata(file);
int ret;
/* Do not change the format while stream is on */
if (vb2_is_busy(&vcap->queue))
return -EBUSY;
ret = dcmipp_bytecap_try_fmt_vid_cap(file, priv, f);
if (ret)
return ret;
dev_dbg(vcap->dev, "%s: format update: old:%ux%u (0x%p4cc, %u, %u, %u, %u) new:%ux%d (0x%p4cc, %u, %u, %u, %u)\n",
vcap->vdev.name,
/* old */
vcap->format.width, vcap->format.height,
&vcap->format.pixelformat, vcap->format.colorspace,
vcap->format.quantization, vcap->format.xfer_func,
vcap->format.ycbcr_enc,
/* new */
f->fmt.pix.width, f->fmt.pix.height,
&f->fmt.pix.pixelformat, f->fmt.pix.colorspace,
f->fmt.pix.quantization, f->fmt.pix.xfer_func,
f->fmt.pix.ycbcr_enc);
vcap->format = f->fmt.pix;
return 0;
}
static int dcmipp_bytecap_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
const struct dcmipp_bytecap_pix_map *vpix;
unsigned int index = f->index;
unsigned int i, prev_pixelformat = 0;
/*
* List up all formats (or only ones matching f->mbus_code), taking
* care of removing duplicated entries (due to support of both
* parallel & csi 16 bits formats
*/
for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) {
vpix = &dcmipp_bytecap_pix_map_list[i];
/* Skip formats not matching requested mbus code */
if (f->mbus_code && vpix->code != f->mbus_code)
continue;
/* Skip duplicated pixelformat */
if (vpix->pixelformat == prev_pixelformat)
continue;
prev_pixelformat = vpix->pixelformat;
if (index == 0)
break;
index--;
}
if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list))
return -EINVAL;
f->pixelformat = vpix->pixelformat;
return 0;
}
static int dcmipp_bytecap_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
const struct dcmipp_bytecap_pix_map *vpix;
if (fsize->index)
return -EINVAL;
/* Only accept code in the pix map table */
vpix = dcmipp_bytecap_pix_map_by_pixelformat(fsize->pixel_format);
if (!vpix)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
fsize->stepwise.min_width = DCMIPP_FRAME_MIN_WIDTH;
fsize->stepwise.max_width = DCMIPP_FRAME_MAX_WIDTH;
fsize->stepwise.min_height = DCMIPP_FRAME_MIN_HEIGHT;
fsize->stepwise.max_height = DCMIPP_FRAME_MAX_HEIGHT;
fsize->stepwise.step_width = 1;
fsize->stepwise.step_height = 1;
return 0;
}
static const struct v4l2_file_operations dcmipp_bytecap_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
static const struct v4l2_ioctl_ops dcmipp_bytecap_ioctl_ops = {
.vidioc_querycap = dcmipp_bytecap_querycap,
.vidioc_g_fmt_vid_cap = dcmipp_bytecap_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = dcmipp_bytecap_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = dcmipp_bytecap_try_fmt_vid_cap,
.vidioc_enum_fmt_vid_cap = dcmipp_bytecap_enum_fmt_vid_cap,
.vidioc_enum_framesizes = dcmipp_bytecap_enum_framesizes,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
static void dcmipp_start_capture(struct dcmipp_bytecap_device *vcap,
struct dcmipp_buf *buf)
{
/* Set buffer address */
reg_write(vcap, DCMIPP_P0PPM0AR1, buf->addr);
/* Set buffer size */
reg_write(vcap, DCMIPP_P0DCLMTR, DCMIPP_P0DCLMTR_ENABLE |
((buf->size / 4) & DCMIPP_P0DCLMTR_LIMIT_MASK));
/* Capture request */
reg_set(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
}
static void dcmipp_bytecap_all_buffers_done(struct dcmipp_bytecap_device *vcap,
enum vb2_buffer_state state)
{
struct dcmipp_buf *buf, *node;
list_for_each_entry_safe(buf, node, &vcap->buffers, list) {
list_del_init(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, state);
}
}
static int dcmipp_bytecap_start_streaming(struct vb2_queue *vq,
unsigned int count)
{
struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
struct media_entity *entity = &vcap->vdev.entity;
struct dcmipp_buf *buf;
struct media_pad *pad;
int ret;
vcap->sequence = 0;
memset(&vcap->count, 0, sizeof(vcap->count));
/*
* Get source subdev - since link is IMMUTABLE, pointer is cached
* within the dcmipp_bytecap_device structure
*/
if (!vcap->s_subdev) {
pad = media_pad_remote_pad_first(&vcap->vdev.entity.pads[0]);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) {
ret = -EINVAL;
goto err_buffer_done;
}
vcap->s_subdev = media_entity_to_v4l2_subdev(pad->entity);
vcap->s_subdev_pad_nb = pad->index;
}
ret = pm_runtime_resume_and_get(vcap->dev);
if (ret < 0) {
dev_err(vcap->dev, "%s: Failed to start streaming, cannot get sync (%d)\n",
__func__, ret);
goto err_buffer_done;
}
ret = media_pipeline_start(entity->pads, &vcap->pipe);
if (ret) {
dev_dbg(vcap->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n",
__func__, ret);
goto err_pm_put;
}
ret = v4l2_subdev_enable_streams(vcap->s_subdev,
vcap->s_subdev_pad_nb, BIT_ULL(0));
if (ret)
goto err_media_pipeline_stop;
spin_lock_irq(&vcap->irqlock);
/* Enable pipe at the end of programming */
reg_set(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
/*
* vb2 framework guarantee that we have at least 'min_queued_buffers'
* buffers in the list at this moment
*/
vcap->next = list_first_entry(&vcap->buffers, typeof(*buf), list);
dev_dbg(vcap->dev, "Start with next [%d] %p phy=%pad\n",
vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr);
dcmipp_start_capture(vcap, vcap->next);
/* Enable interruptions */
reg_set(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL);
vcap->state = DCMIPP_RUNNING;
spin_unlock_irq(&vcap->irqlock);
return 0;
err_media_pipeline_stop:
media_pipeline_stop(entity->pads);
err_pm_put:
pm_runtime_put(vcap->dev);
err_buffer_done:
spin_lock_irq(&vcap->irqlock);
/*
* Return all buffers to vb2 in QUEUED state.
* This will give ownership back to userspace
*/
dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_QUEUED);
vcap->active = NULL;
spin_unlock_irq(&vcap->irqlock);
return ret;
}
static void dcmipp_dump_status(struct dcmipp_bytecap_device *vcap)
{
struct device *dev = vcap->dev;
dev_dbg(dev, "[DCMIPP_PRSR] =%#10.8x\n", reg_read(vcap, DCMIPP_PRSR));
dev_dbg(dev, "[DCMIPP_P0SR] =%#10.8x\n", reg_read(vcap, DCMIPP_P0SR));
dev_dbg(dev, "[DCMIPP_P0DCCNTR]=%#10.8x\n",
reg_read(vcap, DCMIPP_P0DCCNTR));
dev_dbg(dev, "[DCMIPP_CMSR1] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR1));
dev_dbg(dev, "[DCMIPP_CMSR2] =%#10.8x\n", reg_read(vcap, DCMIPP_CMSR2));
}
/*
* Stop the stream engine. Any remaining buffers in the stream queue are
* dequeued and passed on to the vb2 framework marked as STATE_ERROR.
*/
static void dcmipp_bytecap_stop_streaming(struct vb2_queue *vq)
{
struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
int ret;
u32 status;
ret = v4l2_subdev_disable_streams(vcap->s_subdev,
vcap->s_subdev_pad_nb, BIT_ULL(0));
if (ret)
dev_warn(vcap->dev, "Failed to disable stream\n");
/* Stop the media pipeline */
media_pipeline_stop(vcap->vdev.entity.pads);
/* Disable interruptions */
reg_clear(vcap, DCMIPP_CMIER, DCMIPP_CMIER_P0ALL);
/* Stop capture */
reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
/* Wait until CPTACT become 0 */
ret = readl_relaxed_poll_timeout(vcap->regs + DCMIPP_P0SR, status,
!(status & DCMIPP_P0SR_CPTACT),
20 * USEC_PER_MSEC,
1000 * USEC_PER_MSEC);
if (ret)
dev_warn(vcap->dev, "Timeout when stopping\n");
/* Disable pipe */
reg_clear(vcap, DCMIPP_P0FSCR, DCMIPP_P0FSCR_PIPEN);
/* Clear any pending interrupts */
reg_write(vcap, DCMIPP_CMFCR, DCMIPP_CMIER_P0ALL);
spin_lock_irq(&vcap->irqlock);
/* Return all queued buffers to vb2 in ERROR state */
dcmipp_bytecap_all_buffers_done(vcap, VB2_BUF_STATE_ERROR);
INIT_LIST_HEAD(&vcap->buffers);
vcap->active = NULL;
vcap->state = DCMIPP_STOPPED;
spin_unlock_irq(&vcap->irqlock);
dcmipp_dump_status(vcap);
pm_runtime_put(vcap->dev);
if (vcap->count.errors)
dev_warn(vcap->dev, "Some errors found while streaming: errors=%d (overrun=%d, limit=%d, nactive=%d), underrun=%d, buffers=%d\n",
vcap->count.errors, vcap->count.overrun,
vcap->count.limit, vcap->count.nactive,
vcap->count.underrun, vcap->count.buffers);
}
static int dcmipp_bytecap_buf_prepare(struct vb2_buffer *vb)
{
struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
unsigned long size;
size = vcap->format.sizeimage;
if (vb2_plane_size(vb, 0) < size) {
dev_err(vcap->dev, "%s data will not fit into plane (%lu < %lu)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
if (!buf->prepared) {
/* Get memory addresses */
buf->addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0);
buf->prepared = true;
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size);
dev_dbg(vcap->dev, "Setup [%d] phy=%pad size=%zu\n",
vb->index, &buf->addr, buf->size);
}
return 0;
}
static void dcmipp_bytecap_buf_queue(struct vb2_buffer *vb2_buf)
{
struct dcmipp_bytecap_device *vcap =
vb2_get_drv_priv(vb2_buf->vb2_queue);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2_buf);
struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
dev_dbg(vcap->dev, "Queue [%d] %p phy=%pad\n", buf->vb.vb2_buf.index,
buf, &buf->addr);
spin_lock_irq(&vcap->irqlock);
list_add_tail(&buf->list, &vcap->buffers);
if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) {
vcap->next = buf;
dev_dbg(vcap->dev, "Restart with next [%d] %p phy=%pad\n",
buf->vb.vb2_buf.index, buf, &buf->addr);
dcmipp_start_capture(vcap, buf);
vcap->state = DCMIPP_RUNNING;
}
spin_unlock_irq(&vcap->irqlock);
}
static int dcmipp_bytecap_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct dcmipp_bytecap_device *vcap = vb2_get_drv_priv(vq);
unsigned int size;
size = vcap->format.sizeimage;
/* Make sure the image size is large enough */
if (*nplanes)
return sizes[0] < vcap->format.sizeimage ? -EINVAL : 0;
*nplanes = 1;
sizes[0] = vcap->format.sizeimage;
dev_dbg(vcap->dev, "Setup queue, count=%d, size=%d\n",
*nbuffers, size);
return 0;
}
static int dcmipp_bytecap_buf_init(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct dcmipp_buf *buf = container_of(vbuf, struct dcmipp_buf, vb);
INIT_LIST_HEAD(&buf->list);
return 0;
}
static const struct vb2_ops dcmipp_bytecap_qops = {
.start_streaming = dcmipp_bytecap_start_streaming,
.stop_streaming = dcmipp_bytecap_stop_streaming,
.buf_init = dcmipp_bytecap_buf_init,
.buf_prepare = dcmipp_bytecap_buf_prepare,
.buf_queue = dcmipp_bytecap_buf_queue,
.queue_setup = dcmipp_bytecap_queue_setup,
};
static void dcmipp_bytecap_release(struct video_device *vdev)
{
struct dcmipp_bytecap_device *vcap =
container_of(vdev, struct dcmipp_bytecap_device, vdev);
dcmipp_pads_cleanup(vcap->ved.pads);
mutex_destroy(&vcap->lock);
kfree(vcap);
}
void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved)
{
struct dcmipp_bytecap_device *vcap =
container_of(ved, struct dcmipp_bytecap_device, ved);
media_entity_cleanup(ved->ent);
vb2_video_unregister_device(&vcap->vdev);
}
static void dcmipp_buffer_done(struct dcmipp_bytecap_device *vcap,
struct dcmipp_buf *buf,
size_t bytesused,
int err)
{
struct vb2_v4l2_buffer *vbuf;
list_del_init(&buf->list);
vbuf = &buf->vb;
vbuf->sequence = vcap->sequence++;
vbuf->field = V4L2_FIELD_NONE;
vbuf->vb2_buf.timestamp = ktime_get_ns();
vb2_set_plane_payload(&vbuf->vb2_buf, 0, bytesused);
vb2_buffer_done(&vbuf->vb2_buf,
err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dev_dbg(vcap->dev, "Done [%d] %p phy=%pad\n", buf->vb.vb2_buf.index,
buf, &buf->addr);
vcap->count.buffers++;
}
/* irqlock must be held */
static void
dcmipp_bytecap_set_next_frame_or_stop(struct dcmipp_bytecap_device *vcap)
{
if (!vcap->next && list_is_singular(&vcap->buffers)) {
/*
* If there is no available buffer (none or a single one in the
* list while two are expected), stop the capture (effective
* for next frame). On-going frame capture will continue until
* FRAME END but no further capture will be done.
*/
reg_clear(vcap, DCMIPP_P0FCTCR, DCMIPP_P0FCTCR_CPTREQ);
dev_dbg(vcap->dev, "Capture restart is deferred to next buffer queueing\n");
vcap->next = NULL;
vcap->state = DCMIPP_WAIT_FOR_BUFFER;
return;
}
/* If we don't have buffer yet, pick the one after active */
if (!vcap->next)
vcap->next = list_next_entry(vcap->active, list);
/*
* Set buffer address
* This register is shadowed and will be taken into
* account on next VSYNC (start of next frame)
*/
reg_write(vcap, DCMIPP_P0PPM0AR1, vcap->next->addr);
dev_dbg(vcap->dev, "Write [%d] %p phy=%pad\n",
vcap->next->vb.vb2_buf.index, vcap->next, &vcap->next->addr);
}
/* irqlock must be held */
static void dcmipp_bytecap_process_frame(struct dcmipp_bytecap_device *vcap,
size_t bytesused)
{
int err = 0;
struct dcmipp_buf *buf = vcap->active;
if (!buf) {
vcap->count.nactive++;
vcap->count.errors++;
return;
}
if (bytesused > buf->size) {
dev_dbg(vcap->dev, "frame larger than expected (%zu > %zu)\n",
bytesused, buf->size);
/* Clip to buffer size and return buffer to V4L2 in error */
bytesused = buf->size;
vcap->count.limit++;
vcap->count.errors++;
err = -EOVERFLOW;
}
dcmipp_buffer_done(vcap, buf, bytesused, err);
vcap->active = NULL;
}
static irqreturn_t dcmipp_bytecap_irq_thread(int irq, void *arg)
{
struct dcmipp_bytecap_device *vcap =
container_of(arg, struct dcmipp_bytecap_device, ved);
size_t bytesused = 0;
spin_lock_irq(&vcap->irqlock);
/*
* If we have an overrun, a frame-end will probably not be generated,
* in that case the active buffer will be recycled as next buffer by
* the VSYNC handler
*/
if (vcap->cmsr2 & DCMIPP_CMSR2_P0OVRF) {
vcap->count.errors++;
vcap->count.overrun++;
}
if (vcap->cmsr2 & DCMIPP_CMSR2_P0FRAMEF) {
vcap->count.frame++;
/* Read captured buffer size */
bytesused = reg_read(vcap, DCMIPP_P0DCCNTR);
dcmipp_bytecap_process_frame(vcap, bytesused);
}
if (vcap->cmsr2 & DCMIPP_CMSR2_P0VSYNCF) {
vcap->count.vsync++;
if (vcap->state == DCMIPP_WAIT_FOR_BUFFER) {
vcap->count.underrun++;
goto out;
}
/*
* On VSYNC, the previously set next buffer is going to become
* active thanks to the shadowing mechanism of the DCMIPP. In
* most of the cases, since a FRAMEEND has already come,
* pointer next is NULL since active is reset during the
* FRAMEEND handling. However, in case of framerate adjustment,
* there are more VSYNC than FRAMEEND. Thus we recycle the
* active (but not used) buffer and put it back into next.
*/
swap(vcap->active, vcap->next);
dcmipp_bytecap_set_next_frame_or_stop(vcap);
}
out:
spin_unlock_irq(&vcap->irqlock);
return IRQ_HANDLED;
}
static irqreturn_t dcmipp_bytecap_irq_callback(int irq, void *arg)
{
struct dcmipp_bytecap_device *vcap =
container_of(arg, struct dcmipp_bytecap_device, ved);
/* Store interrupt status register */
vcap->cmsr2 = reg_read(vcap, DCMIPP_CMSR2) & DCMIPP_CMIER_P0ALL;
vcap->count.it++;
/* Clear interrupt */
reg_write(vcap, DCMIPP_CMFCR, vcap->cmsr2);
return IRQ_WAKE_THREAD;
}
static int dcmipp_bytecap_link_validate(struct media_link *link)
{
struct media_entity *entity = link->sink->entity;
struct video_device *vd = media_entity_to_video_device(entity);
struct dcmipp_bytecap_device *vcap = container_of(vd,
struct dcmipp_bytecap_device, vdev);
struct v4l2_subdev *source_sd =
media_entity_to_v4l2_subdev(link->source->entity);
struct v4l2_subdev_format source_fmt = {
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
.pad = link->source->index,
};
int ret, i;
ret = v4l2_subdev_call(source_sd, pad, get_fmt, NULL, &source_fmt);
if (ret < 0)
return 0;
if (source_fmt.format.width != vcap->format.width ||
source_fmt.format.height != vcap->format.height) {
dev_err(vcap->dev, "Wrong width or height %ux%u (%ux%u expected)\n",
vcap->format.width, vcap->format.height,
source_fmt.format.width, source_fmt.format.height);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(dcmipp_bytecap_pix_map_list); i++) {
if (dcmipp_bytecap_pix_map_list[i].pixelformat ==
vcap->format.pixelformat &&
dcmipp_bytecap_pix_map_list[i].code ==
source_fmt.format.code)
break;
}
if (i == ARRAY_SIZE(dcmipp_bytecap_pix_map_list)) {
dev_err(vcap->dev, "mbus code 0x%x do not match capture device format (0x%x)\n",
vcap->format.pixelformat, source_fmt.format.code);
return -EINVAL;
}
return 0;
}
static const struct media_entity_operations dcmipp_bytecap_entity_ops = {
.link_validate = dcmipp_bytecap_link_validate,
};
struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev,
const char *entity_name,
struct v4l2_device *v4l2_dev,
void __iomem *regs)
{
struct dcmipp_bytecap_device *vcap;
struct video_device *vdev;
struct vb2_queue *q;
const unsigned long pad_flag = MEDIA_PAD_FL_SINK;
int ret = 0;
/* Allocate the dcmipp_bytecap_device struct */
vcap = kzalloc_obj(*vcap);
if (!vcap)
return ERR_PTR(-ENOMEM);
/* Allocate the pads */
vcap->ved.pads = dcmipp_pads_init(1, &pad_flag);
if (IS_ERR(vcap->ved.pads)) {
ret = PTR_ERR(vcap->ved.pads);
goto err_free_vcap;
}
/* Initialize the media entity */
vcap->vdev.entity.name = entity_name;
vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
vcap->vdev.entity.ops = &dcmipp_bytecap_entity_ops;
ret = media_entity_pads_init(&vcap->vdev.entity, 1, vcap->ved.pads);
if (ret)
goto err_clean_pads;
/* Initialize the lock */
mutex_init(&vcap->lock);
/* Initialize the vb2 queue */
q = &vcap->queue;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->lock = &vcap->lock;
q->drv_priv = vcap;
q->buf_struct_size = sizeof(struct dcmipp_buf);
q->ops = &dcmipp_bytecap_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->min_queued_buffers = 1;
q->dev = dev;
/* DCMIPP requires 16 bytes aligned buffers */
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(dev, "Failed to set DMA mask\n");
goto err_mutex_destroy;
}
ret = vb2_queue_init(q);
if (ret) {
dev_err(dev, "%s: vb2 queue init failed (err=%d)\n",
entity_name, ret);
goto err_clean_m_ent;
}
/* Initialize buffer list and its lock */
INIT_LIST_HEAD(&vcap->buffers);
spin_lock_init(&vcap->irqlock);
/* Set default frame format */
vcap->format = fmt_default;
/* Fill the dcmipp_ent_device struct */
vcap->ved.ent = &vcap->vdev.entity;
vcap->ved.handler = dcmipp_bytecap_irq_callback;
vcap->ved.thread_fn = dcmipp_bytecap_irq_thread;
vcap->dev = dev;
vcap->regs = regs;
/* Initialize the video_device struct */
vdev = &vcap->vdev;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
V4L2_CAP_IO_MC;
vdev->release = dcmipp_bytecap_release;
vdev->fops = &dcmipp_bytecap_fops;
vdev->ioctl_ops = &dcmipp_bytecap_ioctl_ops;
vdev->lock = &vcap->lock;
vdev->queue = q;
vdev->v4l2_dev = v4l2_dev;
strscpy(vdev->name, entity_name, sizeof(vdev->name));
video_set_drvdata(vdev, &vcap->ved);
/* Register the video_device with the v4l2 and the media framework */
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(dev, "%s: video register failed (err=%d)\n",
vcap->vdev.name, ret);
goto err_clean_m_ent;
}
return &vcap->ved;
err_clean_m_ent:
media_entity_cleanup(&vcap->vdev.entity);
err_mutex_destroy:
mutex_destroy(&vcap->lock);
err_clean_pads:
dcmipp_pads_cleanup(vcap->ved.pads);
err_free_vcap:
kfree(vcap);
return ERR_PTR(ret);
}
@@ -0,0 +1,606 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#include <linux/vmalloc.h>
#include <linux/v4l2-mediabus.h>
#include <media/v4l2-rect.h>
#include <media/v4l2-subdev.h>
#include "dcmipp-common.h"
#define DCMIPP_P0FCTCR 0x500
#define DCMIPP_P0FCTCR_FRATE_MASK GENMASK(1, 0)
#define DCMIPP_P0SCSTR 0x504
#define DCMIPP_P0SCSTR_HSTART_SHIFT 0
#define DCMIPP_P0SCSTR_VSTART_SHIFT 16
#define DCMIPP_P0SCSZR 0x508
#define DCMIPP_P0SCSZR_ENABLE BIT(31)
#define DCMIPP_P0SCSZR_HSIZE_SHIFT 0
#define DCMIPP_P0SCSZR_VSIZE_SHIFT 16
#define DCMIPP_P0PPCR 0x5c0
#define DCMIPP_P0PPCR_BSM_1_2 0x1
#define DCMIPP_P0PPCR_BSM_1_4 0x2
#define DCMIPP_P0PPCR_BSM_2_4 0x3
#define DCMIPP_P0PPCR_BSM_MASK GENMASK(8, 7)
#define DCMIPP_P0PPCR_BSM_SHIFT 0x7
#define DCMIPP_P0PPCR_LSM BIT(10)
#define DCMIPP_P0PPCR_OELS BIT(11)
#define IS_SINK(pad) (!(pad))
#define IS_SRC(pad) ((pad))
struct dcmipp_byteproc_pix_map {
unsigned int code;
unsigned int bpp;
};
#define PIXMAP_MBUS_BPP(mbus, byteperpixel) \
{ \
.code = MEDIA_BUS_FMT_##mbus, \
.bpp = byteperpixel, \
}
static const struct dcmipp_byteproc_pix_map dcmipp_byteproc_pix_map_list[] = {
PIXMAP_MBUS_BPP(RGB565_2X8_LE, 2),
PIXMAP_MBUS_BPP(RGB565_1X16, 2),
PIXMAP_MBUS_BPP(RGB888_3X8, 3),
PIXMAP_MBUS_BPP(RGB888_1X24, 3),
PIXMAP_MBUS_BPP(YUYV8_2X8, 2),
PIXMAP_MBUS_BPP(YUYV8_1X16, 2),
PIXMAP_MBUS_BPP(YVYU8_2X8, 2),
PIXMAP_MBUS_BPP(YVYU8_1X16, 2),
PIXMAP_MBUS_BPP(UYVY8_2X8, 2),
PIXMAP_MBUS_BPP(UYVY8_1X16, 2),
PIXMAP_MBUS_BPP(VYUY8_2X8, 2),
PIXMAP_MBUS_BPP(VYUY8_1X16, 2),
PIXMAP_MBUS_BPP(Y8_1X8, 1),
PIXMAP_MBUS_BPP(Y10_1X10, 2),
PIXMAP_MBUS_BPP(Y12_1X12, 2),
PIXMAP_MBUS_BPP(Y14_1X14, 2),
PIXMAP_MBUS_BPP(SBGGR8_1X8, 1),
PIXMAP_MBUS_BPP(SGBRG8_1X8, 1),
PIXMAP_MBUS_BPP(SGRBG8_1X8, 1),
PIXMAP_MBUS_BPP(SRGGB8_1X8, 1),
PIXMAP_MBUS_BPP(SBGGR10_1X10, 2),
PIXMAP_MBUS_BPP(SGBRG10_1X10, 2),
PIXMAP_MBUS_BPP(SGRBG10_1X10, 2),
PIXMAP_MBUS_BPP(SRGGB10_1X10, 2),
PIXMAP_MBUS_BPP(SBGGR12_1X12, 2),
PIXMAP_MBUS_BPP(SGBRG12_1X12, 2),
PIXMAP_MBUS_BPP(SGRBG12_1X12, 2),
PIXMAP_MBUS_BPP(SRGGB12_1X12, 2),
PIXMAP_MBUS_BPP(SBGGR14_1X14, 2),
PIXMAP_MBUS_BPP(SGBRG14_1X14, 2),
PIXMAP_MBUS_BPP(SGRBG14_1X14, 2),
PIXMAP_MBUS_BPP(SRGGB14_1X14, 2),
PIXMAP_MBUS_BPP(JPEG_1X8, 1),
};
static const struct dcmipp_byteproc_pix_map *
dcmipp_byteproc_pix_map_by_code(u32 code)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(dcmipp_byteproc_pix_map_list); i++) {
if (dcmipp_byteproc_pix_map_list[i].code == code)
return &dcmipp_byteproc_pix_map_list[i];
}
return NULL;
}
struct dcmipp_byteproc_device {
struct dcmipp_ent_device ved;
struct v4l2_subdev sd;
struct device *dev;
void __iomem *regs;
};
static const struct v4l2_mbus_framefmt fmt_default = {
.width = DCMIPP_FMT_WIDTH_DEFAULT,
.height = DCMIPP_FMT_HEIGHT_DEFAULT,
.code = MEDIA_BUS_FMT_RGB565_2X8_LE,
.field = V4L2_FIELD_NONE,
.colorspace = DCMIPP_COLORSPACE_DEFAULT,
.ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
.quantization = DCMIPP_QUANTIZATION_DEFAULT,
.xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
};
static const struct v4l2_rect crop_min = {
.width = DCMIPP_FRAME_MIN_WIDTH,
.height = DCMIPP_FRAME_MIN_HEIGHT,
.top = 0,
.left = 0,
};
static void dcmipp_byteproc_adjust_crop(struct v4l2_rect *r,
struct v4l2_rect *compose)
{
/* Disallow rectangles smaller than the minimal one. */
v4l2_rect_set_min_size(r, &crop_min);
v4l2_rect_map_inside(r, compose);
}
static void dcmipp_byteproc_adjust_compose(struct v4l2_rect *r,
const struct v4l2_mbus_framefmt *fmt)
{
const struct dcmipp_byteproc_pix_map *vpix;
r->top = 0;
r->left = 0;
/* Compose is not possible for JPEG or Bayer formats */
if (fmt->code >= MEDIA_BUS_FMT_SBGGR8_1X8 &&
fmt->code <= MEDIA_BUS_FMT_JPEG_1X8) {
r->width = fmt->width;
r->height = fmt->height;
return;
}
/* Prevent compose on formats which are not 1 or 2 bytes per pixel */
vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
if (vpix->bpp != 1 && vpix->bpp != 2) {
r->width = fmt->width;
r->height = fmt->height;
return;
}
/* Adjust height - we can only perform 1/2 decimation */
if (r->height <= (fmt->height / 2))
r->height = fmt->height / 2;
else
r->height = fmt->height;
/* Adjust width /2 or /4 for 8bits formats and /2 for 16bits formats */
if (vpix->bpp == 1 && r->width <= (fmt->width / 4))
r->width = fmt->width / 4;
else if (r->width <= (fmt->width / 2))
r->width = fmt->width / 2;
else
r->width = fmt->width;
}
static void dcmipp_byteproc_adjust_fmt(struct v4l2_mbus_framefmt *fmt)
{
const struct dcmipp_byteproc_pix_map *vpix;
/* Only accept code in the pix map table */
vpix = dcmipp_byteproc_pix_map_by_code(fmt->code);
if (!vpix)
fmt->code = fmt_default.code;
fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
DCMIPP_FRAME_MAX_WIDTH) & ~1;
fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
DCMIPP_FRAME_MAX_HEIGHT) & ~1;
if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
fmt->field = fmt_default.field;
dcmipp_colorimetry_clamp(fmt);
}
static int dcmipp_byteproc_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state)
{
unsigned int i;
for (i = 0; i < sd->entity.num_pads; i++) {
struct v4l2_mbus_framefmt *mf;
struct v4l2_rect *r;
mf = v4l2_subdev_state_get_format(sd_state, i);
*mf = fmt_default;
if (IS_SINK(i))
r = v4l2_subdev_state_get_compose(sd_state, i);
else
r = v4l2_subdev_state_get_crop(sd_state, i);
r->top = 0;
r->left = 0;
r->width = DCMIPP_FMT_WIDTH_DEFAULT;
r->height = DCMIPP_FMT_HEIGHT_DEFAULT;
}
return 0;
}
static int
dcmipp_byteproc_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
const struct dcmipp_byteproc_pix_map *vpix;
struct v4l2_mbus_framefmt *sink_fmt;
if (IS_SINK(code->pad)) {
if (code->index >= ARRAY_SIZE(dcmipp_byteproc_pix_map_list))
return -EINVAL;
vpix = &dcmipp_byteproc_pix_map_list[code->index];
code->code = vpix->code;
} else {
/* byteproc doesn't support transformation on format */
if (code->index > 0)
return -EINVAL;
sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
code->code = sink_fmt->code;
}
return 0;
}
static int
dcmipp_byteproc_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
struct v4l2_rect *compose;
if (fse->index)
return -EINVAL;
fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
if (IS_SINK(fse->pad)) {
fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
} else {
compose = v4l2_subdev_state_get_compose(sd_state, 0);
fse->max_width = compose->width;
fse->max_height = compose->height;
}
return 0;
}
static int dcmipp_byteproc_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct v4l2_mbus_framefmt *mf;
struct v4l2_rect *crop, *compose;
if (v4l2_subdev_is_streaming(sd))
return -EBUSY;
mf = v4l2_subdev_state_get_format(sd_state, fmt->pad);
crop = v4l2_subdev_state_get_crop(sd_state, 1);
compose = v4l2_subdev_state_get_compose(sd_state, 0);
if (IS_SRC(fmt->pad)) {
fmt->format = *v4l2_subdev_state_get_format(sd_state, 0);
fmt->format.width = crop->width;
fmt->format.height = crop->height;
} else {
dcmipp_byteproc_adjust_fmt(&fmt->format);
crop->top = 0;
crop->left = 0;
crop->width = fmt->format.width;
crop->height = fmt->format.height;
*compose = *crop;
/* Set the same format on SOURCE pad as well */
*v4l2_subdev_state_get_format(sd_state, 1) = fmt->format;
}
*mf = fmt->format;
return 0;
}
static int dcmipp_byteproc_get_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *s)
{
struct v4l2_mbus_framefmt *sink_fmt;
struct v4l2_rect *crop, *compose;
/*
* In the HW, the decimation block is located prior to the crop hence:
* Compose is done on the sink pad
* Crop is done on the src pad
*/
if (IS_SINK(s->pad) &&
(s->target == V4L2_SEL_TGT_CROP ||
s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
s->target == V4L2_SEL_TGT_CROP_DEFAULT))
return -EINVAL;
if (IS_SRC(s->pad) &&
(s->target == V4L2_SEL_TGT_COMPOSE ||
s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT))
return -EINVAL;
sink_fmt = v4l2_subdev_state_get_format(sd_state, 0);
crop = v4l2_subdev_state_get_crop(sd_state, 1);
compose = v4l2_subdev_state_get_compose(sd_state, 0);
switch (s->target) {
case V4L2_SEL_TGT_CROP:
s->r = *crop;
break;
case V4L2_SEL_TGT_CROP_BOUNDS:
case V4L2_SEL_TGT_CROP_DEFAULT:
s->r = *compose;
break;
case V4L2_SEL_TGT_COMPOSE:
s->r = *compose;
break;
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
case V4L2_SEL_TGT_COMPOSE_DEFAULT:
s->r.top = 0;
s->r.left = 0;
s->r.width = sink_fmt->width;
s->r.height = sink_fmt->height;
break;
default:
return -EINVAL;
}
return 0;
}
static int dcmipp_byteproc_set_selection(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_selection *s)
{
struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *mf;
struct v4l2_rect *crop, *compose;
/*
* In the HW, the decimation block is located prior to the crop hence:
* Compose is done on the sink pad
* Crop is done on the src pad
*/
if ((s->target == V4L2_SEL_TGT_CROP ||
s->target == V4L2_SEL_TGT_CROP_BOUNDS ||
s->target == V4L2_SEL_TGT_CROP_DEFAULT) && IS_SINK(s->pad))
return -EINVAL;
if ((s->target == V4L2_SEL_TGT_COMPOSE ||
s->target == V4L2_SEL_TGT_COMPOSE_BOUNDS ||
s->target == V4L2_SEL_TGT_COMPOSE_DEFAULT) && IS_SRC(s->pad))
return -EINVAL;
crop = v4l2_subdev_state_get_crop(sd_state, 1);
compose = v4l2_subdev_state_get_compose(sd_state, 0);
switch (s->target) {
case V4L2_SEL_TGT_CROP:
dcmipp_byteproc_adjust_crop(&s->r, compose);
*crop = s->r;
mf = v4l2_subdev_state_get_format(sd_state, 1);
mf->width = s->r.width;
mf->height = s->r.height;
dev_dbg(byteproc->dev, "s_selection: crop (%d,%d)/%ux%u\n",
crop->left, crop->top, crop->width, crop->height);
break;
case V4L2_SEL_TGT_COMPOSE:
mf = v4l2_subdev_state_get_format(sd_state, 0);
dcmipp_byteproc_adjust_compose(&s->r, mf);
*compose = s->r;
*crop = s->r;
mf = v4l2_subdev_state_get_format(sd_state, 1);
mf->width = s->r.width;
mf->height = s->r.height;
dev_dbg(byteproc->dev, "s_selection: compose (%d,%d)/%ux%u\n",
compose->left, compose->top,
compose->width, compose->height);
break;
default:
return -EINVAL;
}
return 0;
}
static int dcmipp_byteproc_configure_scale_crop
(struct dcmipp_byteproc_device *byteproc,
struct v4l2_subdev_state *state)
{
const struct dcmipp_byteproc_pix_map *vpix;
struct v4l2_mbus_framefmt *sink_fmt;
u32 hprediv, vprediv;
struct v4l2_rect *compose, *crop;
u32 val = 0;
sink_fmt = v4l2_subdev_state_get_format(state, 0);
compose = v4l2_subdev_state_get_compose(state, 0);
crop = v4l2_subdev_state_get_crop(state, 1);
/* find output format bpp */
vpix = dcmipp_byteproc_pix_map_by_code(sink_fmt->code);
if (!vpix)
return -EINVAL;
/* clear decimation/crop */
reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_BSM_MASK);
reg_clear(byteproc, DCMIPP_P0PPCR, DCMIPP_P0PPCR_LSM);
reg_write(byteproc, DCMIPP_P0SCSTR, 0);
reg_write(byteproc, DCMIPP_P0SCSZR, 0);
/* Ignore decimation/crop with JPEG */
if (vpix->code == MEDIA_BUS_FMT_JPEG_1X8)
return 0;
/* decimation */
hprediv = sink_fmt->width / compose->width;
if (hprediv == 4)
val |= DCMIPP_P0PPCR_BSM_1_4 << DCMIPP_P0PPCR_BSM_SHIFT;
else if ((vpix->code == MEDIA_BUS_FMT_Y8_1X8) && (hprediv == 2))
val |= DCMIPP_P0PPCR_BSM_1_2 << DCMIPP_P0PPCR_BSM_SHIFT;
else if (hprediv == 2)
val |= DCMIPP_P0PPCR_BSM_2_4 << DCMIPP_P0PPCR_BSM_SHIFT;
vprediv = sink_fmt->height / compose->height;
if (vprediv == 2)
val |= DCMIPP_P0PPCR_LSM | DCMIPP_P0PPCR_OELS;
/* decimate using bytes and lines skipping */
if (val) {
reg_set(byteproc, DCMIPP_P0PPCR, val);
dev_dbg(byteproc->dev, "decimate to %dx%d [prediv=%dx%d]\n",
compose->width, compose->height,
hprediv, vprediv);
}
dev_dbg(byteproc->dev, "crop to %dx%d\n", crop->width, crop->height);
/* expressed in 32-bits words on X axis, lines on Y axis */
reg_write(byteproc, DCMIPP_P0SCSTR,
(((crop->left * vpix->bpp) / 4) <<
DCMIPP_P0SCSTR_HSTART_SHIFT) |
(crop->top << DCMIPP_P0SCSTR_VSTART_SHIFT));
reg_write(byteproc, DCMIPP_P0SCSZR,
DCMIPP_P0SCSZR_ENABLE |
(((crop->width * vpix->bpp) / 4) <<
DCMIPP_P0SCSZR_HSIZE_SHIFT) |
(crop->height << DCMIPP_P0SCSZR_VSIZE_SHIFT));
return 0;
}
static int dcmipp_byteproc_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
struct v4l2_subdev *s_subdev;
struct media_pad *s_pad;
int ret;
/* Get source subdev */
s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
return -EINVAL;
s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);
ret = dcmipp_byteproc_configure_scale_crop(byteproc, state);
if (ret)
return ret;
ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0));
if (ret < 0) {
dev_err(byteproc->dev,
"failed to start source subdev streaming (%d)\n", ret);
return ret;
}
return 0;
}
static int dcmipp_byteproc_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
struct v4l2_subdev *s_subdev;
struct media_pad *s_pad;
int ret;
/* Get source subdev */
s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
return -EINVAL;
s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);
ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0));
if (ret < 0) {
dev_err(byteproc->dev,
"failed to start source subdev streaming (%d)\n", ret);
return ret;
}
return 0;
}
static const struct v4l2_subdev_pad_ops dcmipp_byteproc_pad_ops = {
.enum_mbus_code = dcmipp_byteproc_enum_mbus_code,
.enum_frame_size = dcmipp_byteproc_enum_frame_size,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = dcmipp_byteproc_set_fmt,
.get_selection = dcmipp_byteproc_get_selection,
.set_selection = dcmipp_byteproc_set_selection,
.enable_streams = dcmipp_byteproc_enable_streams,
.disable_streams = dcmipp_byteproc_disable_streams,
};
static const struct v4l2_subdev_video_ops dcmipp_byteproc_video_ops = {
.s_stream = v4l2_subdev_s_stream_helper,
};
static const struct v4l2_subdev_ops dcmipp_byteproc_ops = {
.pad = &dcmipp_byteproc_pad_ops,
.video = &dcmipp_byteproc_video_ops,
};
static void dcmipp_byteproc_release(struct v4l2_subdev *sd)
{
struct dcmipp_byteproc_device *byteproc = v4l2_get_subdevdata(sd);
kfree(byteproc);
}
static const struct v4l2_subdev_internal_ops dcmipp_byteproc_int_ops = {
.init_state = dcmipp_byteproc_init_state,
.release = dcmipp_byteproc_release,
};
void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved)
{
struct dcmipp_byteproc_device *byteproc =
container_of(ved, struct dcmipp_byteproc_device, ved);
dcmipp_ent_sd_unregister(ved, &byteproc->sd);
}
struct dcmipp_ent_device *
dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name,
struct v4l2_device *v4l2_dev, void __iomem *regs)
{
struct dcmipp_byteproc_device *byteproc;
const unsigned long pads_flag[] = {
MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
};
int ret;
/* Allocate the byteproc struct */
byteproc = kzalloc_obj(*byteproc);
if (!byteproc)
return ERR_PTR(-ENOMEM);
byteproc->regs = regs;
/* Initialize ved and sd */
ret = dcmipp_ent_sd_register(&byteproc->ved, &byteproc->sd,
v4l2_dev, entity_name,
MEDIA_ENT_F_PROC_VIDEO_SCALER,
ARRAY_SIZE(pads_flag), pads_flag,
&dcmipp_byteproc_int_ops,
&dcmipp_byteproc_ops,
NULL, NULL);
if (ret) {
kfree(byteproc);
return ERR_PTR(ret);
}
byteproc->dev = dev;
return &byteproc->ved;
}
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#include <linux/init.h>
#include <linux/module.h>
#include "dcmipp-common.h"
/* Helper function to allocate and initialize pads */
struct media_pad *dcmipp_pads_init(u16 num_pads, const unsigned long *pads_flags)
{
struct media_pad *pads;
unsigned int i;
/* Allocate memory for the pads */
pads = kzalloc_objs(*pads, num_pads);
if (!pads)
return ERR_PTR(-ENOMEM);
/* Initialize the pads */
for (i = 0; i < num_pads; i++) {
pads[i].index = i;
pads[i].flags = pads_flags[i];
}
return pads;
}
static const struct media_entity_operations dcmipp_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};
int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
struct v4l2_subdev *sd,
struct v4l2_device *v4l2_dev,
const char *const name,
u32 function,
u16 num_pads,
const unsigned long *pads_flag,
const struct v4l2_subdev_internal_ops *sd_int_ops,
const struct v4l2_subdev_ops *sd_ops,
irq_handler_t handler,
irq_handler_t thread_fn)
{
int ret;
/* Allocate the pads. Should be released from the sd_int_op release */
ved->pads = dcmipp_pads_init(num_pads, pads_flag);
if (IS_ERR(ved->pads))
return PTR_ERR(ved->pads);
/* Fill the dcmipp_ent_device struct */
ved->ent = &sd->entity;
/* Initialize the subdev */
v4l2_subdev_init(sd, sd_ops);
sd->internal_ops = sd_int_ops;
sd->entity.function = function;
sd->entity.ops = &dcmipp_entity_ops;
sd->owner = THIS_MODULE;
strscpy(sd->name, name, sizeof(sd->name));
v4l2_set_subdevdata(sd, ved);
/* Expose this subdev to user space */
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
if (sd->ctrl_handler)
sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
/* Initialize the media entity */
ret = media_entity_pads_init(&sd->entity, num_pads, ved->pads);
if (ret)
goto err_clean_pads;
ret = v4l2_subdev_init_finalize(sd);
if (ret < 0)
goto err_clean_m_ent;
/* Register the subdev with the v4l2 and the media framework */
ret = v4l2_device_register_subdev(v4l2_dev, sd);
if (ret) {
dev_err(v4l2_dev->dev,
"%s: subdev register failed (err=%d)\n",
name, ret);
goto err_clean_m_ent;
}
ved->handler = handler;
ved->thread_fn = thread_fn;
return 0;
err_clean_m_ent:
media_entity_cleanup(&sd->entity);
err_clean_pads:
dcmipp_pads_cleanup(ved->pads);
return ret;
}
void
dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved, struct v4l2_subdev *sd)
{
media_entity_cleanup(ved->ent);
v4l2_device_unregister_subdev(sd);
}
@@ -0,0 +1,217 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#ifndef _DCMIPP_COMMON_H_
#define _DCMIPP_COMMON_H_
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <media/media-device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#define DCMIPP_PDEV_NAME "dcmipp"
#define DCMIPP_FRAME_MAX_WIDTH 4096
#define DCMIPP_FRAME_MAX_HEIGHT 2160
#define DCMIPP_FRAME_MIN_WIDTH 16
#define DCMIPP_FRAME_MIN_HEIGHT 16
#define DCMIPP_FMT_WIDTH_DEFAULT 640
#define DCMIPP_FMT_HEIGHT_DEFAULT 480
#define DCMIPP_COLORSPACE_DEFAULT V4L2_COLORSPACE_REC709
#define DCMIPP_YCBCR_ENC_DEFAULT V4L2_YCBCR_ENC_DEFAULT
#define DCMIPP_QUANTIZATION_DEFAULT V4L2_QUANTIZATION_DEFAULT
#define DCMIPP_XFER_FUNC_DEFAULT V4L2_XFER_FUNC_DEFAULT
/**
* dcmipp_colorimetry_clamp() - Adjust colorimetry parameters
*
* @fmt: the pointer to struct v4l2_pix_format or
* struct v4l2_mbus_framefmt
*
* Entities must check if colorimetry given by the userspace is valid, if not
* then set them as DEFAULT
*/
#define dcmipp_colorimetry_clamp(fmt) \
do { \
if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT || \
(fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) { \
(fmt)->colorspace = DCMIPP_COLORSPACE_DEFAULT; \
(fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT; \
(fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT; \
(fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT; \
} \
if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \
(fmt)->ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT; \
if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \
(fmt)->quantization = DCMIPP_QUANTIZATION_DEFAULT; \
if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \
(fmt)->xfer_func = DCMIPP_XFER_FUNC_DEFAULT; \
} while (0)
/**
* struct dcmipp_ent_device - core struct that represents a node in the topology
*
* @ent: the pointer to struct media_entity for the node
* @pads: the list of pads of the node
* @bus: struct v4l2_mbus_config_parallel describing input bus
* @bus_type: type of input bus (parallel or BT656)
* @handler: irq handler dedicated to the subdev
* @handler_ret: value returned by the irq handler
* @thread_fn: threaded irq handler
*
* The DCMIPP provides a single IRQ line and a IRQ status registers for all
* subdevs, hence once the main irq handler (registered at probe time) is
* called, it will chain calls to the irq handler of each the subdevs of the
* pipelines, using the handler/handler_ret/thread_fn variables.
*
* Each node of the topology must create a dcmipp_ent_device struct.
* Depending on the node it will be of an instance of v4l2_subdev or
* video_device struct where both contains a struct media_entity.
* Those structures should embedded the dcmipp_ent_device struct through
* v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the
* dcmipp_ent_device struct to be retrieved from the corresponding struct
* media_entity
*/
struct dcmipp_ent_device {
struct media_entity *ent;
struct media_pad *pads;
/* Parallel input device */
struct v4l2_mbus_config_parallel bus;
enum v4l2_mbus_type bus_type;
irq_handler_t handler;
irqreturn_t handler_ret;
irq_handler_t thread_fn;
};
/**
* dcmipp_pads_init - initialize pads
*
* @num_pads: number of pads to initialize
* @pads_flags: flags to use in each pad
*
* Helper functions to allocate/initialize pads
*/
struct media_pad *dcmipp_pads_init(u16 num_pads,
const unsigned long *pads_flags);
/**
* dcmipp_pads_cleanup - free pads
*
* @pads: pointer to the pads
*
* Helper function to free the pads initialized with dcmipp_pads_init
*/
static inline void dcmipp_pads_cleanup(struct media_pad *pads)
{
kfree(pads);
}
/**
* dcmipp_ent_sd_register - initialize and register a subdev node
*
* @ved: the dcmipp_ent_device struct to be initialize
* @sd: the v4l2_subdev struct to be initialize and registered
* @v4l2_dev: the v4l2 device to register the v4l2_subdev
* @name: name of the sub-device. Please notice that the name must be
* unique.
* @function: media entity function defined by MEDIA_ENT_F_* macros
* @num_pads: number of pads to initialize
* @pads_flag: flags to use in each pad
* @sd_int_ops: pointer to &struct v4l2_subdev_internal_ops
* @sd_ops: pointer to &struct v4l2_subdev_ops.
* @handler: func pointer of the irq handler
* @thread_fn: func pointer of the threaded irq handler
*
* Helper function initialize and register the struct dcmipp_ent_device and
* struct v4l2_subdev which represents a subdev node in the topology
*/
int dcmipp_ent_sd_register(struct dcmipp_ent_device *ved,
struct v4l2_subdev *sd,
struct v4l2_device *v4l2_dev,
const char *const name,
u32 function,
u16 num_pads,
const unsigned long *pads_flag,
const struct v4l2_subdev_internal_ops *sd_int_ops,
const struct v4l2_subdev_ops *sd_ops,
irq_handler_t handler,
irq_handler_t thread_fn);
/**
* dcmipp_ent_sd_unregister - cleanup and unregister a subdev node
*
* @ved: the dcmipp_ent_device struct to be cleaned up
* @sd: the v4l2_subdev struct to be unregistered
*
* Helper function cleanup and unregister the struct dcmipp_ent_device and
* struct v4l2_subdev which represents a subdev node in the topology
*/
void dcmipp_ent_sd_unregister(struct dcmipp_ent_device *ved,
struct v4l2_subdev *sd);
#define reg_write(device, reg, val) \
(__reg_write((device)->dev, (device)->regs, (reg), (val)))
#define reg_read(device, reg) \
(__reg_read((device)->dev, (device)->regs, (reg)))
#define reg_set(device, reg, mask) \
(__reg_set((device)->dev, (device)->regs, (reg), (mask)))
#define reg_clear(device, reg, mask) \
(__reg_clear((device)->dev, (device)->regs, (reg), (mask)))
static inline u32 __reg_read(struct device *dev, void __iomem *base, u32 reg)
{
u32 val = readl_relaxed(base + reg);
dev_dbg(dev, "RD 0x%x %#10.8x\n", reg, val);
return val;
}
static inline void __reg_write(struct device *dev, void __iomem *base, u32 reg,
u32 val)
{
dev_dbg(dev, "WR 0x%x %#10.8x\n", reg, val);
writel_relaxed(val, base + reg);
}
static inline void __reg_set(struct device *dev, void __iomem *base, u32 reg,
u32 mask)
{
dev_dbg(dev, "SET 0x%x %#10.8x\n", reg, mask);
__reg_write(dev, base, reg, readl_relaxed(base + reg) | mask);
}
static inline void __reg_clear(struct device *dev, void __iomem *base, u32 reg,
u32 mask)
{
dev_dbg(dev, "CLR 0x%x %#10.8x\n", reg, mask);
__reg_write(dev, base, reg, readl_relaxed(base + reg) & ~mask);
}
/* DCMIPP subdev init / release entry points */
struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev,
const char *entity_name,
struct v4l2_device *v4l2_dev,
void __iomem *regs);
void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved);
struct dcmipp_ent_device *
dcmipp_byteproc_ent_init(struct device *dev, const char *entity_name,
struct v4l2_device *v4l2_dev, void __iomem *regs);
void dcmipp_byteproc_ent_release(struct dcmipp_ent_device *ved);
struct dcmipp_ent_device *dcmipp_bytecap_ent_init(struct device *dev,
const char *entity_name,
struct v4l2_device *v4l2_dev,
void __iomem *regs);
void dcmipp_bytecap_ent_release(struct dcmipp_ent_device *ved);
#endif
@@ -0,0 +1,677 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/reset.h>
#include <media/media-device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include "dcmipp-common.h"
#define DCMIPP_MDEV_MODEL_NAME "DCMIPP MDEV"
#define DCMIPP_ENT_LINK(src, srcpad, sink, sinkpad, link_flags) { \
.src_ent = src, \
.src_pad = srcpad, \
.sink_ent = sink, \
.sink_pad = sinkpad, \
.flags = link_flags, \
}
struct dcmipp_device {
/* The platform device */
struct platform_device pdev;
struct device *dev;
/* Hardware resources */
void __iomem *regs;
struct clk *mclk;
struct clk *kclk;
/* The pipeline configuration */
const struct dcmipp_pipeline_config *pipe_cfg;
/* The Associated media_device parent */
struct media_device mdev;
/* Internal v4l2 parent device*/
struct v4l2_device v4l2_dev;
/* Entities */
struct dcmipp_ent_device **entity;
struct v4l2_async_notifier notifier;
};
static inline struct dcmipp_device *
notifier_to_dcmipp(struct v4l2_async_notifier *n)
{
return container_of(n, struct dcmipp_device, notifier);
}
/* Structure which describes individual configuration for each entity */
struct dcmipp_ent_config {
const char *name;
struct dcmipp_ent_device *(*init)
(struct device *dev, const char *entity_name,
struct v4l2_device *v4l2_dev, void __iomem *regs);
void (*release)(struct dcmipp_ent_device *ved);
};
/* Structure which describes links between entities */
struct dcmipp_ent_link {
unsigned int src_ent;
u16 src_pad;
unsigned int sink_ent;
u16 sink_pad;
u32 flags;
};
/* Structure which describes the whole topology */
struct dcmipp_pipeline_config {
const struct dcmipp_ent_config *ents;
size_t num_ents;
const struct dcmipp_ent_link *links;
size_t num_links;
u32 hw_revision;
bool has_csi2;
bool needs_mclk;
};
/* --------------------------------------------------------------------------
* Topology Configuration
*/
static const struct dcmipp_ent_config stm32mp13_ent_config[] = {
{
.name = "dcmipp_input",
.init = dcmipp_inp_ent_init,
.release = dcmipp_inp_ent_release,
},
{
.name = "dcmipp_dump_postproc",
.init = dcmipp_byteproc_ent_init,
.release = dcmipp_byteproc_ent_release,
},
{
.name = "dcmipp_dump_capture",
.init = dcmipp_bytecap_ent_init,
.release = dcmipp_bytecap_ent_release,
},
};
#define ID_INPUT 0
#define ID_DUMP_BYTEPROC 1
#define ID_DUMP_CAPTURE 2
static const struct dcmipp_ent_link stm32mp13_ent_links[] = {
DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0,
MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0,
MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
};
#define DCMIPP_STM32MP13_VERR 0x10
static const struct dcmipp_pipeline_config stm32mp13_pipe_cfg = {
.ents = stm32mp13_ent_config,
.num_ents = ARRAY_SIZE(stm32mp13_ent_config),
.links = stm32mp13_ent_links,
.num_links = ARRAY_SIZE(stm32mp13_ent_links),
.hw_revision = DCMIPP_STM32MP13_VERR
};
static const struct dcmipp_ent_config stm32mp25_ent_config[] = {
{
.name = "dcmipp_input",
.init = dcmipp_inp_ent_init,
.release = dcmipp_inp_ent_release,
},
{
.name = "dcmipp_dump_postproc",
.init = dcmipp_byteproc_ent_init,
.release = dcmipp_byteproc_ent_release,
},
{
.name = "dcmipp_dump_capture",
.init = dcmipp_bytecap_ent_init,
.release = dcmipp_bytecap_ent_release,
},
};
static const struct dcmipp_ent_link stm32mp25_ent_links[] = {
DCMIPP_ENT_LINK(ID_INPUT, 1, ID_DUMP_BYTEPROC, 0,
MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
DCMIPP_ENT_LINK(ID_DUMP_BYTEPROC, 1, ID_DUMP_CAPTURE, 0,
MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE),
};
#define DCMIPP_STM32MP25_VERR 0x30
static const struct dcmipp_pipeline_config stm32mp25_pipe_cfg = {
.ents = stm32mp25_ent_config,
.num_ents = ARRAY_SIZE(stm32mp25_ent_config),
.links = stm32mp25_ent_links,
.num_links = ARRAY_SIZE(stm32mp25_ent_links),
.hw_revision = DCMIPP_STM32MP25_VERR,
.has_csi2 = true,
.needs_mclk = true
};
#define LINK_FLAG_TO_STR(f) ((f) == 0 ? "" :\
(f) == MEDIA_LNK_FL_ENABLED ? "ENABLED" :\
(f) == MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" :\
(f) == (MEDIA_LNK_FL_ENABLED |\
MEDIA_LNK_FL_IMMUTABLE) ?\
"ENABLED, IMMUTABLE" :\
"UNKNOWN")
static int dcmipp_create_links(struct dcmipp_device *dcmipp)
{
unsigned int i;
int ret;
/* Initialize the links between entities */
for (i = 0; i < dcmipp->pipe_cfg->num_links; i++) {
const struct dcmipp_ent_link *link =
&dcmipp->pipe_cfg->links[i];
struct dcmipp_ent_device *ved_src =
dcmipp->entity[link->src_ent];
struct dcmipp_ent_device *ved_sink =
dcmipp->entity[link->sink_ent];
dev_dbg(dcmipp->dev, "Create link \"%s\":%d -> %d:\"%s\" [%s]\n",
dcmipp->pipe_cfg->ents[link->src_ent].name,
link->src_pad, link->sink_pad,
dcmipp->pipe_cfg->ents[link->sink_ent].name,
LINK_FLAG_TO_STR(link->flags));
ret = media_create_pad_link(ved_src->ent, link->src_pad,
ved_sink->ent, link->sink_pad,
link->flags);
if (ret)
return ret;
}
return 0;
}
static int dcmipp_graph_init(struct dcmipp_device *dcmipp);
static int dcmipp_create_subdevs(struct dcmipp_device *dcmipp)
{
int ret, i;
/* Call all subdev inits */
for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
const char *name = dcmipp->pipe_cfg->ents[i].name;
dev_dbg(dcmipp->dev, "add subdev %s\n", name);
dcmipp->entity[i] =
dcmipp->pipe_cfg->ents[i].init(dcmipp->dev, name,
&dcmipp->v4l2_dev,
dcmipp->regs);
if (IS_ERR(dcmipp->entity[i])) {
dev_err(dcmipp->dev, "failed to init subdev %s\n",
name);
ret = PTR_ERR(dcmipp->entity[i]);
goto err_init_entity;
}
}
/* Initialize links */
ret = dcmipp_create_links(dcmipp);
if (ret)
goto err_init_entity;
ret = dcmipp_graph_init(dcmipp);
if (ret < 0)
goto err_init_entity;
return 0;
err_init_entity:
while (i-- > 0)
dcmipp->pipe_cfg->ents[i].release(dcmipp->entity[i]);
return ret;
}
static const struct of_device_id dcmipp_of_match[] = {
{ .compatible = "st,stm32mp13-dcmipp", .data = &stm32mp13_pipe_cfg },
{ .compatible = "st,stm32mp25-dcmipp", .data = &stm32mp25_pipe_cfg },
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, dcmipp_of_match);
static irqreturn_t dcmipp_irq_thread(int irq, void *arg)
{
struct dcmipp_device *dcmipp = arg;
struct dcmipp_ent_device *ved;
unsigned int i;
/* Call irq thread of each entities of pipeline */
for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
ved = dcmipp->entity[i];
if (ved->thread_fn && ved->handler_ret == IRQ_WAKE_THREAD)
ved->thread_fn(irq, ved);
}
return IRQ_HANDLED;
}
static irqreturn_t dcmipp_irq_callback(int irq, void *arg)
{
struct dcmipp_device *dcmipp = arg;
struct dcmipp_ent_device *ved;
irqreturn_t ret = IRQ_HANDLED;
unsigned int i;
/* Call irq handler of each entities of pipeline */
for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++) {
ved = dcmipp->entity[i];
if (ved->handler)
ved->handler_ret = ved->handler(irq, ved);
else if (ved->thread_fn)
ved->handler_ret = IRQ_WAKE_THREAD;
else
ved->handler_ret = IRQ_HANDLED;
if (ved->handler_ret != IRQ_HANDLED)
ret = ved->handler_ret;
}
return ret;
}
static int dcmipp_graph_notify_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *subdev,
struct v4l2_async_connection *asd)
{
struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
int ret = -EINVAL;
int src_pad, i;
struct dcmipp_ent_device *sink;
struct v4l2_fwnode_endpoint vep = { 0 };
struct fwnode_handle *ep;
enum v4l2_mbus_type supported_types[] = {
V4L2_MBUS_PARALLEL, V4L2_MBUS_BT656, V4L2_MBUS_CSI2_DPHY
};
dev_dbg(dcmipp->dev, "Subdev \"%s\" bound\n", subdev->name);
/*
* Link this sub-device to DCMIPP, it could be
* a parallel camera sensor or a CSI-2 to parallel bridge
*/
src_pad = media_entity_get_fwnode_pad(&subdev->entity,
subdev->fwnode,
MEDIA_PAD_FL_SOURCE);
/* Get bus characteristics from devicetree */
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep) {
dev_err(dcmipp->dev, "Could not find the endpoint\n");
return -ENODEV;
}
/* Check for supported MBUS type */
for (i = 0; i < ARRAY_SIZE(supported_types); i++) {
/* Only MP25 supports CSI input */
if (supported_types[i] == V4L2_MBUS_CSI2_DPHY &&
!dcmipp->pipe_cfg->has_csi2)
continue;
vep.bus_type = supported_types[i];
ret = v4l2_fwnode_endpoint_parse(ep, &vep);
if (!ret)
break;
}
fwnode_handle_put(ep);
if (ret) {
dev_err(dcmipp->dev, "Could not parse the endpoint\n");
return ret;
}
if (vep.bus_type != V4L2_MBUS_CSI2_DPHY &&
vep.bus.parallel.bus_width == 0) {
dev_err(dcmipp->dev, "Invalid parallel interface bus-width\n");
return -ENODEV;
}
/* Only 8 bits bus width supported with BT656 bus */
if (vep.bus_type == V4L2_MBUS_BT656 &&
vep.bus.parallel.bus_width != 8) {
dev_err(dcmipp->dev, "BT656 bus conflicts with %u bits bus width (8 bits required)\n",
vep.bus.parallel.bus_width);
return -ENODEV;
}
/* Connect input device to the dcmipp_input subdev */
sink = dcmipp->entity[ID_INPUT];
if (vep.bus_type != V4L2_MBUS_CSI2_DPHY) {
sink->bus.flags = vep.bus.parallel.flags;
sink->bus.bus_width = vep.bus.parallel.bus_width;
sink->bus.data_shift = vep.bus.parallel.data_shift;
}
sink->bus_type = vep.bus_type;
ret = media_create_pad_link(&subdev->entity, src_pad, sink->ent, 0,
MEDIA_LNK_FL_IMMUTABLE |
MEDIA_LNK_FL_ENABLED);
if (ret) {
dev_err(dcmipp->dev, "Failed to create media pad link with subdev \"%s\"\n",
subdev->name);
return ret;
}
dev_dbg(dcmipp->dev, "DCMIPP is now linked to \"%s\"\n", subdev->name);
return 0;
}
static void dcmipp_graph_notify_unbind(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_connection *asd)
{
struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
dev_dbg(dcmipp->dev, "Removing %s\n", sd->name);
}
static int dcmipp_graph_notify_complete(struct v4l2_async_notifier *notifier)
{
struct dcmipp_device *dcmipp = notifier_to_dcmipp(notifier);
int ret;
/* Register the media device */
ret = media_device_register(&dcmipp->mdev);
if (ret) {
dev_err(dcmipp->mdev.dev,
"media device register failed (err=%d)\n", ret);
return ret;
}
/* Expose all subdev's nodes*/
ret = v4l2_device_register_subdev_nodes(&dcmipp->v4l2_dev);
if (ret) {
dev_err(dcmipp->mdev.dev,
"dcmipp subdev nodes registration failed (err=%d)\n",
ret);
media_device_unregister(&dcmipp->mdev);
return ret;
}
dev_dbg(dcmipp->dev, "Notify complete !\n");
return 0;
}
static const struct v4l2_async_notifier_operations dcmipp_graph_notify_ops = {
.bound = dcmipp_graph_notify_bound,
.unbind = dcmipp_graph_notify_unbind,
.complete = dcmipp_graph_notify_complete,
};
static int dcmipp_graph_init(struct dcmipp_device *dcmipp)
{
struct v4l2_async_connection *asd;
struct fwnode_handle *ep;
int ret;
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dcmipp->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep) {
dev_err(dcmipp->dev, "Failed to get next endpoint\n");
return -EINVAL;
}
v4l2_async_nf_init(&dcmipp->notifier, &dcmipp->v4l2_dev);
asd = v4l2_async_nf_add_fwnode_remote(&dcmipp->notifier, ep,
struct v4l2_async_connection);
fwnode_handle_put(ep);
if (IS_ERR(asd)) {
dev_err(dcmipp->dev, "Failed to add fwnode remote subdev\n");
return PTR_ERR(asd);
}
dcmipp->notifier.ops = &dcmipp_graph_notify_ops;
ret = v4l2_async_nf_register(&dcmipp->notifier);
if (ret < 0) {
dev_err(dcmipp->dev, "Failed to register notifier\n");
v4l2_async_nf_cleanup(&dcmipp->notifier);
return ret;
}
return 0;
}
static int dcmipp_probe(struct platform_device *pdev)
{
struct dcmipp_device *dcmipp;
struct clk *kclk, *mclk;
const struct dcmipp_pipeline_config *pipe_cfg;
struct reset_control *rstc;
int irq;
int ret;
dcmipp = devm_kzalloc(&pdev->dev, sizeof(*dcmipp), GFP_KERNEL);
if (!dcmipp)
return -ENOMEM;
dcmipp->dev = &pdev->dev;
pipe_cfg = device_get_match_data(dcmipp->dev);
if (!pipe_cfg) {
dev_err(&pdev->dev, "Can't get device data\n");
return -ENODEV;
}
dcmipp->pipe_cfg = pipe_cfg;
platform_set_drvdata(pdev, dcmipp);
/* Get hardware resources from devicetree */
rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
if (IS_ERR(rstc))
return dev_err_probe(&pdev->dev, PTR_ERR(rstc),
"Could not get reset control\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
dcmipp->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(dcmipp->regs)) {
dev_err(&pdev->dev, "Could not map registers\n");
return PTR_ERR(dcmipp->regs);
}
ret = devm_request_threaded_irq(&pdev->dev, irq, dcmipp_irq_callback,
dcmipp_irq_thread, IRQF_ONESHOT,
dev_name(&pdev->dev), dcmipp);
if (ret) {
dev_err(&pdev->dev, "Unable to request irq %d\n", irq);
return ret;
}
/* Reset device */
ret = reset_control_assert(rstc);
if (ret) {
dev_err(&pdev->dev, "Failed to assert the reset line\n");
return ret;
}
usleep_range(3000, 5000);
ret = reset_control_deassert(rstc);
if (ret) {
dev_err(&pdev->dev, "Failed to deassert the reset line\n");
return ret;
}
/*
* In case of the DCMIPP has only 1 clock (such as on MP13), the
* clock might not be named.
*/
kclk = devm_clk_get(&pdev->dev,
dcmipp->pipe_cfg->needs_mclk ? "kclk" : NULL);
if (IS_ERR(kclk))
return dev_err_probe(&pdev->dev, PTR_ERR(kclk),
"Unable to get kclk\n");
dcmipp->kclk = kclk;
if (dcmipp->pipe_cfg->needs_mclk) {
mclk = devm_clk_get(&pdev->dev, "mclk");
if (IS_ERR(mclk))
return dev_err_probe(&pdev->dev, PTR_ERR(mclk),
"Unable to get mclk\n");
dcmipp->mclk = mclk;
}
dcmipp->entity = devm_kcalloc(&pdev->dev, dcmipp->pipe_cfg->num_ents,
sizeof(*dcmipp->entity), GFP_KERNEL);
if (!dcmipp->entity)
return -ENOMEM;
/* Register the v4l2 struct */
ret = v4l2_device_register(&pdev->dev, &dcmipp->v4l2_dev);
if (ret) {
dev_err(&pdev->dev,
"v4l2 device register failed (err=%d)\n", ret);
return ret;
}
/* Link the media device within the v4l2_device */
dcmipp->v4l2_dev.mdev = &dcmipp->mdev;
/* Initialize media device */
strscpy(dcmipp->mdev.model, DCMIPP_MDEV_MODEL_NAME,
sizeof(dcmipp->mdev.model));
dcmipp->mdev.hw_revision = pipe_cfg->hw_revision;
dcmipp->mdev.dev = &pdev->dev;
media_device_init(&dcmipp->mdev);
/* Initialize subdevs */
ret = dcmipp_create_subdevs(dcmipp);
if (ret) {
media_device_cleanup(&dcmipp->mdev);
v4l2_device_unregister(&dcmipp->v4l2_dev);
return ret;
}
pm_runtime_enable(dcmipp->dev);
dev_info(&pdev->dev, "Probe done");
return 0;
}
static void dcmipp_remove(struct platform_device *pdev)
{
struct dcmipp_device *dcmipp = platform_get_drvdata(pdev);
unsigned int i;
pm_runtime_disable(&pdev->dev);
v4l2_async_nf_unregister(&dcmipp->notifier);
v4l2_async_nf_cleanup(&dcmipp->notifier);
for (i = 0; i < dcmipp->pipe_cfg->num_ents; i++)
dcmipp->pipe_cfg->ents[i].release(dcmipp->entity[i]);
media_device_unregister(&dcmipp->mdev);
media_device_cleanup(&dcmipp->mdev);
v4l2_device_unregister(&dcmipp->v4l2_dev);
}
static int dcmipp_runtime_suspend(struct device *dev)
{
struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
clk_disable_unprepare(dcmipp->kclk);
clk_disable_unprepare(dcmipp->mclk);
return 0;
}
static int dcmipp_runtime_resume(struct device *dev)
{
struct dcmipp_device *dcmipp = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(dcmipp->mclk);
if (ret) {
dev_err(dev, "%s: Failed to prepare_enable mclk\n", __func__);
return ret;
}
ret = clk_prepare_enable(dcmipp->kclk);
if (ret) {
clk_disable_unprepare(dcmipp->mclk);
dev_err(dev, "%s: Failed to prepare_enable kclk\n", __func__);
}
return ret;
}
static int dcmipp_suspend(struct device *dev)
{
/* disable clock */
pm_runtime_force_suspend(dev);
/* change pinctrl state */
pinctrl_pm_select_sleep_state(dev);
return 0;
}
static int dcmipp_resume(struct device *dev)
{
/* restore pinctl default state */
pinctrl_pm_select_default_state(dev);
/* clock enable */
pm_runtime_force_resume(dev);
return 0;
}
static const struct dev_pm_ops dcmipp_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dcmipp_suspend, dcmipp_resume)
RUNTIME_PM_OPS(dcmipp_runtime_suspend, dcmipp_runtime_resume, NULL)
};
static struct platform_driver dcmipp_pdrv = {
.probe = dcmipp_probe,
.remove = dcmipp_remove,
.driver = {
.name = DCMIPP_PDEV_NAME,
.of_match_table = dcmipp_of_match,
.pm = pm_ptr(&dcmipp_pm_ops),
},
};
module_platform_driver(dcmipp_pdrv);
MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@foss.st.com>");
MODULE_AUTHOR("Alain Volmat <alain.volmat@foss.st.com>");
MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface with Pixel Processor driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for STM32 Digital Camera Memory Interface Pixel Processor
*
* Copyright (C) STMicroelectronics SA 2023
* Authors: Hugues Fruchet <hugues.fruchet@foss.st.com>
* Alain Volmat <alain.volmat@foss.st.com>
* for STMicroelectronics.
*/
#include <linux/v4l2-mediabus.h>
#include <media/mipi-csi2.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>
#include "dcmipp-common.h"
#define DCMIPP_PRCR 0x104
#define DCMIPP_PRCR_FORMAT_SHIFT 16
#define DCMIPP_PRCR_FORMAT_YUV422 0x1e
#define DCMIPP_PRCR_FORMAT_RGB565 0x22
#define DCMIPP_PRCR_FORMAT_RGB888 0x24
#define DCMIPP_PRCR_FORMAT_RAW8 0x2a
#define DCMIPP_PRCR_FORMAT_RAW10 0x2b
#define DCMIPP_PRCR_FORMAT_RAW12 0x2c
#define DCMIPP_PRCR_FORMAT_RAW14 0x2d
#define DCMIPP_PRCR_FORMAT_G8 0x4a
#define DCMIPP_PRCR_FORMAT_G10 0x4b
#define DCMIPP_PRCR_FORMAT_G12 0x4c
#define DCMIPP_PRCR_FORMAT_G14 0x4d
#define DCMIPP_PRCR_FORMAT_BYTE_STREAM 0x5a
#define DCMIPP_PRCR_ESS BIT(4)
#define DCMIPP_PRCR_PCKPOL BIT(5)
#define DCMIPP_PRCR_HSPOL BIT(6)
#define DCMIPP_PRCR_VSPOL BIT(7)
#define DCMIPP_PRCR_ENABLE BIT(14)
#define DCMIPP_PRCR_SWAPCYCLES BIT(25)
#define DCMIPP_PRESCR 0x108
#define DCMIPP_PRESUR 0x10c
#define DCMIPP_CMCR 0x204
#define DCMIPP_CMCR_INSEL BIT(0)
#define DCMIPP_P0FSCR 0x404
#define DCMIPP_P0FSCR_DTMODE_MASK GENMASK(17, 16)
#define DCMIPP_P0FSCR_DTMODE_SHIFT 16
#define DCMIPP_P0FSCR_DTMODE_DTIDA 0x00
#define DCMIPP_P0FSCR_DTMODE_ALLDT 0x03
#define DCMIPP_P0FSCR_DTIDA_MASK GENMASK(5, 0)
#define DCMIPP_P0FSCR_DTIDA_SHIFT 0
#define IS_SINK(pad) (!(pad))
#define IS_SRC(pad) ((pad))
struct dcmipp_inp_pix_map {
unsigned int code_sink;
unsigned int code_src;
/* Parallel related information */
u8 prcr_format;
u8 prcr_swapcycles;
/* CSI related information */
unsigned int dt;
};
#define PIXMAP_SINK_SRC_PRCR_SWAP(sink, src, prcr, swap, data_type) \
{ \
.code_sink = MEDIA_BUS_FMT_##sink, \
.code_src = MEDIA_BUS_FMT_##src, \
.prcr_format = DCMIPP_PRCR_FORMAT_##prcr, \
.prcr_swapcycles = swap, \
.dt = data_type, \
}
static const struct dcmipp_inp_pix_map dcmipp_inp_pix_map_list[] = {
/* RGB565 */
PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_LE, RGB565_2X8_LE, RGB565, 1, MIPI_CSI2_DT_RGB565),
PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_2X8_BE, RGB565_2X8_LE, RGB565, 0, MIPI_CSI2_DT_RGB565),
PIXMAP_SINK_SRC_PRCR_SWAP(RGB565_1X16, RGB565_1X16, RGB565, 0, MIPI_CSI2_DT_RGB565),
/* RGB888 */
PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_3X8, RGB888_3X8, RGB888, 0, MIPI_CSI2_DT_RGB888),
PIXMAP_SINK_SRC_PRCR_SWAP(RGB888_1X24, RGB888_1X24, RGB888, 0, MIPI_CSI2_DT_RGB888),
/* YUV422 */
PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, YUYV8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_1X16, YUYV8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(YUYV8_2X8, UYVY8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, UYVY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_1X16, UYVY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(UYVY8_2X8, YUYV8_2X8, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_2X8, YVYU8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(YVYU8_1X16, YVYU8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_2X8, VYUY8_2X8, YUV422, 1, MIPI_CSI2_DT_YUV422_8B),
PIXMAP_SINK_SRC_PRCR_SWAP(VYUY8_1X16, VYUY8_1X16, YUV422, 0, MIPI_CSI2_DT_YUV422_8B),
/* GREY */
PIXMAP_SINK_SRC_PRCR_SWAP(Y8_1X8, Y8_1X8, G8, 0, MIPI_CSI2_DT_RAW8),
PIXMAP_SINK_SRC_PRCR_SWAP(Y10_1X10, Y10_1X10, G10, 0, MIPI_CSI2_DT_RAW10),
PIXMAP_SINK_SRC_PRCR_SWAP(Y12_1X12, Y12_1X12, G12, 0, MIPI_CSI2_DT_RAW12),
PIXMAP_SINK_SRC_PRCR_SWAP(Y14_1X14, Y14_1X14, G14, 0, MIPI_CSI2_DT_RAW14),
/* Raw Bayer */
PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR8_1X8, SBGGR8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8),
PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG8_1X8, SGBRG8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8),
PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG8_1X8, SGRBG8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8),
PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB8_1X8, SRGGB8_1X8, RAW8, 0, MIPI_CSI2_DT_RAW8),
PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR10_1X10, SBGGR10_1X10, RAW10, 0, MIPI_CSI2_DT_RAW10),
PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG10_1X10, SGBRG10_1X10, RAW10, 0, MIPI_CSI2_DT_RAW10),
PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG10_1X10, SGRBG10_1X10, RAW10, 0, MIPI_CSI2_DT_RAW10),
PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB10_1X10, SRGGB10_1X10, RAW10, 0, MIPI_CSI2_DT_RAW10),
PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR12_1X12, SBGGR12_1X12, RAW12, 0, MIPI_CSI2_DT_RAW12),
PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG12_1X12, SGBRG12_1X12, RAW12, 0, MIPI_CSI2_DT_RAW12),
PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG12_1X12, SGRBG12_1X12, RAW12, 0, MIPI_CSI2_DT_RAW12),
PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB12_1X12, SRGGB12_1X12, RAW12, 0, MIPI_CSI2_DT_RAW12),
PIXMAP_SINK_SRC_PRCR_SWAP(SBGGR14_1X14, SBGGR14_1X14, RAW14, 0, MIPI_CSI2_DT_RAW14),
PIXMAP_SINK_SRC_PRCR_SWAP(SGBRG14_1X14, SGBRG14_1X14, RAW14, 0, MIPI_CSI2_DT_RAW14),
PIXMAP_SINK_SRC_PRCR_SWAP(SGRBG14_1X14, SGRBG14_1X14, RAW14, 0, MIPI_CSI2_DT_RAW14),
PIXMAP_SINK_SRC_PRCR_SWAP(SRGGB14_1X14, SRGGB14_1X14, RAW14, 0, MIPI_CSI2_DT_RAW14),
/* JPEG */
PIXMAP_SINK_SRC_PRCR_SWAP(JPEG_1X8, JPEG_1X8, BYTE_STREAM, 0, 0),
};
/*
* Search through the pix_map table, skipping two consecutive entry with the
* same code
*/
static inline const struct dcmipp_inp_pix_map *dcmipp_inp_pix_map_by_index
(unsigned int index,
unsigned int pad)
{
unsigned int i = 0;
u32 prev_code = 0, cur_code;
while (i < ARRAY_SIZE(dcmipp_inp_pix_map_list)) {
if (IS_SRC(pad))
cur_code = dcmipp_inp_pix_map_list[i].code_src;
else
cur_code = dcmipp_inp_pix_map_list[i].code_sink;
if (cur_code == prev_code) {
i++;
continue;
}
prev_code = cur_code;
if (index == 0)
break;
i++;
index--;
}
if (i >= ARRAY_SIZE(dcmipp_inp_pix_map_list))
return NULL;
return &dcmipp_inp_pix_map_list[i];
}
static inline const struct dcmipp_inp_pix_map *dcmipp_inp_pix_map_by_code
(u32 code_sink, u32 code_src)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(dcmipp_inp_pix_map_list); i++) {
if ((dcmipp_inp_pix_map_list[i].code_sink == code_sink &&
dcmipp_inp_pix_map_list[i].code_src == code_src) ||
(dcmipp_inp_pix_map_list[i].code_sink == code_src &&
dcmipp_inp_pix_map_list[i].code_src == code_sink) ||
(dcmipp_inp_pix_map_list[i].code_sink == code_sink &&
code_src == 0) ||
(code_sink == 0 &&
dcmipp_inp_pix_map_list[i].code_src == code_src))
return &dcmipp_inp_pix_map_list[i];
}
return NULL;
}
struct dcmipp_inp_device {
struct dcmipp_ent_device ved;
struct v4l2_subdev sd;
struct device *dev;
void __iomem *regs;
};
static const struct v4l2_mbus_framefmt fmt_default = {
.width = DCMIPP_FMT_WIDTH_DEFAULT,
.height = DCMIPP_FMT_HEIGHT_DEFAULT,
.code = MEDIA_BUS_FMT_RGB565_2X8_LE,
.field = V4L2_FIELD_NONE,
.colorspace = DCMIPP_COLORSPACE_DEFAULT,
.ycbcr_enc = DCMIPP_YCBCR_ENC_DEFAULT,
.quantization = DCMIPP_QUANTIZATION_DEFAULT,
.xfer_func = DCMIPP_XFER_FUNC_DEFAULT,
};
static int dcmipp_inp_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state)
{
unsigned int i;
for (i = 0; i < sd->entity.num_pads; i++) {
struct v4l2_mbus_framefmt *mf;
mf = v4l2_subdev_state_get_format(sd_state, i);
*mf = fmt_default;
}
return 0;
}
static int dcmipp_inp_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
{
const struct dcmipp_inp_pix_map *vpix =
dcmipp_inp_pix_map_by_index(code->index, code->pad);
if (!vpix)
return -EINVAL;
code->code = IS_SRC(code->pad) ? vpix->code_src : vpix->code_sink;
return 0;
}
static int dcmipp_inp_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
const struct dcmipp_inp_pix_map *vpix;
if (fse->index)
return -EINVAL;
/* Only accept code in the pix map table */
vpix = dcmipp_inp_pix_map_by_code(IS_SINK(fse->pad) ? fse->code : 0,
IS_SRC(fse->pad) ? fse->code : 0);
if (!vpix)
return -EINVAL;
fse->min_width = DCMIPP_FRAME_MIN_WIDTH;
fse->max_width = DCMIPP_FRAME_MAX_WIDTH;
fse->min_height = DCMIPP_FRAME_MIN_HEIGHT;
fse->max_height = DCMIPP_FRAME_MAX_HEIGHT;
return 0;
}
static void dcmipp_inp_adjust_fmt(struct dcmipp_inp_device *inp,
struct v4l2_mbus_framefmt *fmt, __u32 pad)
{
const struct dcmipp_inp_pix_map *vpix;
/* Only accept code in the pix map table */
vpix = dcmipp_inp_pix_map_by_code(IS_SINK(pad) ? fmt->code : 0,
IS_SRC(pad) ? fmt->code : 0);
if (!vpix)
fmt->code = fmt_default.code;
/* Exclude JPEG if BT656 bus is selected */
if (vpix && vpix->code_sink == MEDIA_BUS_FMT_JPEG_1X8 &&
inp->ved.bus_type == V4L2_MBUS_BT656)
fmt->code = fmt_default.code;
fmt->width = clamp_t(u32, fmt->width, DCMIPP_FRAME_MIN_WIDTH,
DCMIPP_FRAME_MAX_WIDTH) & ~1;
fmt->height = clamp_t(u32, fmt->height, DCMIPP_FRAME_MIN_HEIGHT,
DCMIPP_FRAME_MAX_HEIGHT) & ~1;
if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE)
fmt->field = fmt_default.field;
dcmipp_colorimetry_clamp(fmt);
}
static int dcmipp_inp_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct dcmipp_inp_device *inp = v4l2_get_subdevdata(sd);
struct v4l2_mbus_framefmt *mf;
if (v4l2_subdev_is_streaming(sd))
return -EBUSY;
mf = v4l2_subdev_state_get_format(sd_state, fmt->pad);
/* Set the new format */
dcmipp_inp_adjust_fmt(inp, &fmt->format, fmt->pad);
dev_dbg(inp->dev, "%s: format update: old:%dx%d (0x%x, %d, %d, %d, %d) new:%dx%d (0x%x, %d, %d, %d, %d)\n",
inp->sd.name,
/* old */
mf->width, mf->height, mf->code,
mf->colorspace, mf->quantization,
mf->xfer_func, mf->ycbcr_enc,
/* new */
fmt->format.width, fmt->format.height, fmt->format.code,
fmt->format.colorspace, fmt->format.quantization,
fmt->format.xfer_func, fmt->format.ycbcr_enc);
*mf = fmt->format;
/* When setting the sink format, report that format on the src pad */
if (IS_SINK(fmt->pad)) {
mf = v4l2_subdev_state_get_format(sd_state, 1);
*mf = fmt->format;
dcmipp_inp_adjust_fmt(inp, mf, 1);
}
return 0;
}
static int dcmipp_inp_configure_parallel(struct dcmipp_inp_device *inp,
struct v4l2_subdev_state *state)
{
u32 val = 0;
const struct dcmipp_inp_pix_map *vpix;
struct v4l2_mbus_framefmt *sink_fmt;
struct v4l2_mbus_framefmt *src_fmt;
/* Set vertical synchronization polarity */
if (inp->ved.bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
val |= DCMIPP_PRCR_VSPOL;
/* Set horizontal synchronization polarity */
if (inp->ved.bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
val |= DCMIPP_PRCR_HSPOL;
/* Set pixel clock polarity */
if (inp->ved.bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING)
val |= DCMIPP_PRCR_PCKPOL;
/*
* BT656 embedded synchronisation bus mode.
*
* Default SAV/EAV mode is supported here with default codes
* SAV=0xff000080 & EAV=0xff00009d.
* With DCMIPP this means LSC=SAV=0x80 & LEC=EAV=0x9d.
*/
if (inp->ved.bus_type == V4L2_MBUS_BT656) {
val |= DCMIPP_PRCR_ESS;
/* Unmask all codes */
reg_write(inp, DCMIPP_PRESUR, 0xffffffff);/* FEC:LEC:LSC:FSC */
/* Trig on LSC=0x80 & LEC=0x9d codes, ignore FSC and FEC */
reg_write(inp, DCMIPP_PRESCR, 0xff9d80ff);/* FEC:LEC:LSC:FSC */
}
/* Set format */
sink_fmt = v4l2_subdev_state_get_format(state, 0);
src_fmt = v4l2_subdev_state_get_format(state, 1);
vpix = dcmipp_inp_pix_map_by_code(sink_fmt->code, src_fmt->code);
if (!vpix) {
dev_err(inp->dev, "Invalid sink/src format configuration\n");
return -EINVAL;
}
val |= vpix->prcr_format << DCMIPP_PRCR_FORMAT_SHIFT;
/* swap cycles */
if (vpix->prcr_swapcycles)
val |= DCMIPP_PRCR_SWAPCYCLES;
reg_write(inp, DCMIPP_PRCR, val);
/* Select the DCMIPP parallel interface */
reg_write(inp, DCMIPP_CMCR, 0);
/* Enable parallel interface */
reg_set(inp, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
return 0;
}
static int dcmipp_inp_configure_csi(struct dcmipp_inp_device *inp,
struct v4l2_subdev_state *state)
{
const struct dcmipp_inp_pix_map *vpix;
struct v4l2_mbus_framefmt *sink_fmt;
struct v4l2_mbus_framefmt *src_fmt;
/* Get format information */
sink_fmt = v4l2_subdev_state_get_format(state, 0);
src_fmt = v4l2_subdev_state_get_format(state, 1);
vpix = dcmipp_inp_pix_map_by_code(sink_fmt->code, src_fmt->code);
if (!vpix) {
dev_err(inp->dev, "Invalid sink/src format configuration\n");
return -EINVAL;
}
/* Apply configuration on each input pipe */
reg_clear(inp, DCMIPP_P0FSCR,
DCMIPP_P0FSCR_DTMODE_MASK | DCMIPP_P0FSCR_DTIDA_MASK);
/* In case of JPEG we don't know the DT so we allow all data */
/*
* TODO - check instead dt == 0 for the time being to allow other
* unknown data-type
*/
if (!vpix->dt)
reg_set(inp, DCMIPP_P0FSCR,
DCMIPP_P0FSCR_DTMODE_ALLDT << DCMIPP_P0FSCR_DTMODE_SHIFT);
else
reg_set(inp, DCMIPP_P0FSCR,
vpix->dt << DCMIPP_P0FSCR_DTIDA_SHIFT |
DCMIPP_P0FSCR_DTMODE_DTIDA);
/* Select the DCMIPP CSI interface */
reg_write(inp, DCMIPP_CMCR, DCMIPP_CMCR_INSEL);
return 0;
}
static int dcmipp_inp_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct dcmipp_inp_device *inp =
container_of(sd, struct dcmipp_inp_device, sd);
struct v4l2_subdev *s_subdev;
struct media_pad *s_pad;
int ret = 0;
/* Get source subdev */
s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
return -EINVAL;
s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);
if (inp->ved.bus_type == V4L2_MBUS_PARALLEL ||
inp->ved.bus_type == V4L2_MBUS_BT656)
ret = dcmipp_inp_configure_parallel(inp, state);
else if (inp->ved.bus_type == V4L2_MBUS_CSI2_DPHY)
ret = dcmipp_inp_configure_csi(inp, state);
if (ret)
return ret;
ret = v4l2_subdev_enable_streams(s_subdev, s_pad->index, BIT_ULL(0));
if (ret < 0) {
dev_err(inp->dev,
"failed to start source subdev streaming (%d)\n", ret);
return ret;
}
return 0;
}
static int dcmipp_inp_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
u32 pad, u64 streams_mask)
{
struct dcmipp_inp_device *inp =
container_of(sd, struct dcmipp_inp_device, sd);
struct v4l2_subdev *s_subdev;
struct media_pad *s_pad;
int ret;
/* Get source subdev */
s_pad = media_pad_remote_pad_first(&sd->entity.pads[0]);
if (!s_pad || !is_media_entity_v4l2_subdev(s_pad->entity))
return -EINVAL;
s_subdev = media_entity_to_v4l2_subdev(s_pad->entity);
ret = v4l2_subdev_disable_streams(s_subdev, s_pad->index, BIT_ULL(0));
if (ret < 0) {
dev_err(inp->dev,
"failed to stop source subdev streaming (%d)\n", ret);
return ret;
}
if (inp->ved.bus_type == V4L2_MBUS_PARALLEL ||
inp->ved.bus_type == V4L2_MBUS_BT656) {
/* Disable parallel interface */
reg_clear(inp, DCMIPP_PRCR, DCMIPP_PRCR_ENABLE);
}
return 0;
}
static const struct v4l2_subdev_pad_ops dcmipp_inp_pad_ops = {
.enum_mbus_code = dcmipp_inp_enum_mbus_code,
.enum_frame_size = dcmipp_inp_enum_frame_size,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = dcmipp_inp_set_fmt,
.enable_streams = dcmipp_inp_enable_streams,
.disable_streams = dcmipp_inp_disable_streams,
};
static const struct v4l2_subdev_video_ops dcmipp_inp_video_ops = {
.s_stream = v4l2_subdev_s_stream_helper,
};
static const struct v4l2_subdev_ops dcmipp_inp_ops = {
.pad = &dcmipp_inp_pad_ops,
.video = &dcmipp_inp_video_ops,
};
static void dcmipp_inp_release(struct v4l2_subdev *sd)
{
struct dcmipp_inp_device *inp =
container_of(sd, struct dcmipp_inp_device, sd);
kfree(inp);
}
static const struct v4l2_subdev_internal_ops dcmipp_inp_int_ops = {
.init_state = dcmipp_inp_init_state,
.release = dcmipp_inp_release,
};
void dcmipp_inp_ent_release(struct dcmipp_ent_device *ved)
{
struct dcmipp_inp_device *inp =
container_of(ved, struct dcmipp_inp_device, ved);
dcmipp_ent_sd_unregister(ved, &inp->sd);
}
struct dcmipp_ent_device *dcmipp_inp_ent_init(struct device *dev,
const char *entity_name,
struct v4l2_device *v4l2_dev,
void __iomem *regs)
{
struct dcmipp_inp_device *inp;
const unsigned long pads_flag[] = {
MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
};
int ret;
/* Allocate the inp struct */
inp = kzalloc_obj(*inp);
if (!inp)
return ERR_PTR(-ENOMEM);
inp->regs = regs;
/* Initialize ved and sd */
ret = dcmipp_ent_sd_register(&inp->ved, &inp->sd, v4l2_dev,
entity_name, MEDIA_ENT_F_VID_IF_BRIDGE,
ARRAY_SIZE(pads_flag), pads_flag,
&dcmipp_inp_int_ops, &dcmipp_inp_ops,
NULL, NULL);
if (ret) {
kfree(inp);
return ERR_PTR(ret);
}
inp->dev = dev;
return &inp->ved;
}