summaryrefslogtreecommitdiff
path: root/drivers/media/platform/sti
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/platform/sti')
-rw-r--r--drivers/media/platform/sti/bdisp/bdisp-debug.c2
-rw-r--r--drivers/media/platform/sti/delta/Makefile6
-rw-r--r--drivers/media/platform/sti/delta/delta-cfg.h64
-rw-r--r--drivers/media/platform/sti/delta/delta-debug.c72
-rw-r--r--drivers/media/platform/sti/delta/delta-debug.h18
-rw-r--r--drivers/media/platform/sti/delta/delta-ipc.c594
-rw-r--r--drivers/media/platform/sti/delta/delta-ipc.h76
-rw-r--r--drivers/media/platform/sti/delta/delta-mem.c51
-rw-r--r--drivers/media/platform/sti/delta/delta-mem.h14
-rw-r--r--drivers/media/platform/sti/delta/delta-mjpeg-dec.c455
-rw-r--r--drivers/media/platform/sti/delta/delta-mjpeg-fw.h225
-rw-r--r--drivers/media/platform/sti/delta/delta-mjpeg-hdr.c149
-rw-r--r--drivers/media/platform/sti/delta/delta-mjpeg.h35
-rw-r--r--drivers/media/platform/sti/delta/delta-v4l2.c1993
-rw-r--r--drivers/media/platform/sti/delta/delta.h566
-rw-r--r--drivers/media/platform/sti/hva/Makefile1
-rw-r--r--drivers/media/platform/sti/hva/hva-debugfs.c422
-rw-r--r--drivers/media/platform/sti/hva/hva-h264.c6
-rw-r--r--drivers/media/platform/sti/hva/hva-hw.c48
-rw-r--r--drivers/media/platform/sti/hva/hva-hw.h3
-rw-r--r--drivers/media/platform/sti/hva/hva-mem.c5
-rw-r--r--drivers/media/platform/sti/hva/hva-v4l2.c78
-rw-r--r--drivers/media/platform/sti/hva/hva.h96
23 files changed, 4964 insertions, 15 deletions
diff --git a/drivers/media/platform/sti/bdisp/bdisp-debug.c b/drivers/media/platform/sti/bdisp/bdisp-debug.c
index 79c56356a7c7..7af66860d624 100644
--- a/drivers/media/platform/sti/bdisp/bdisp-debug.c
+++ b/drivers/media/platform/sti/bdisp/bdisp-debug.c
@@ -677,7 +677,7 @@ int bdisp_debugfs_create(struct bdisp_dev *bdisp)
err:
bdisp_debugfs_remove(bdisp);
- return 0;
+ return -ENOMEM;
}
void bdisp_debugfs_remove(struct bdisp_dev *bdisp)
diff --git a/drivers/media/platform/sti/delta/Makefile b/drivers/media/platform/sti/delta/Makefile
new file mode 100644
index 000000000000..8d032508a933
--- /dev/null
+++ b/drivers/media/platform/sti/delta/Makefile
@@ -0,0 +1,6 @@
+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
diff --git a/drivers/media/platform/sti/delta/delta-cfg.h b/drivers/media/platform/sti/delta/delta-cfg.h
new file mode 100644
index 000000000000..c6388f575800
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-cfg.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-debug.c b/drivers/media/platform/sti/delta/delta-debug.c
new file mode 100644
index 000000000000..a7ebf2cc7783
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-debug.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Hugues Fruchet <hugues.fruchet@st.com>
+ * Fabrice Lecoultre <fabrice.lecoultre@st.com>
+ * for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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%dx%d@(%d,%d) %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.width, s->crop.height,
+ s->crop.left, s->crop.top,
+ 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%dx%d@(%d,%d) %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.width, f->crop.height,
+ f->crop.left, f->crop.top,
+ 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);
+}
diff --git a/drivers/media/platform/sti/delta/delta-debug.h b/drivers/media/platform/sti/delta/delta-debug.h
new file mode 100644
index 000000000000..955c1587ac2d
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-debug.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Hugues Fruchet <hugues.fruchet@st.com>
+ * Fabrice Lecoultre <fabrice.lecoultre@st.com>
+ * for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-ipc.c b/drivers/media/platform/sti/delta/delta-ipc.c
new file mode 100644
index 000000000000..41e4a4c259b3
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-ipc.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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;
+
+ memcpy(msg.name, name, sizeof(msg.name));
+ msg.name[sizeof(msg.name) - 1] = 0;
+
+ 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++;
+ if (ctx->ipc_buf) {
+ 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);
+}
diff --git a/drivers/media/platform/sti/delta/delta-ipc.h b/drivers/media/platform/sti/delta/delta-ipc.h
new file mode 100644
index 000000000000..cef2019c72d4
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-ipc.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-mem.c b/drivers/media/platform/sti/delta/delta-mem.c
new file mode 100644
index 000000000000..d7b53d31caa6
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mem.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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);
+}
diff --git a/drivers/media/platform/sti/delta/delta-mem.h b/drivers/media/platform/sti/delta/delta-mem.h
new file mode 100644
index 000000000000..f8ca109e1241
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mem.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-mjpeg-dec.c b/drivers/media/platform/sti/delta/delta-mjpeg-dec.c
new file mode 100644
index 000000000000..e79bdc611432
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mjpeg-dec.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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, struct delta_au *au)
+{
+ 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 = (u32)(au->paddr);
+ params->picture_end_addr_p = (u32)(au->paddr + au->size - 1);
+
+ /*
+ * !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(sizeof(*ctx), GFP_KERNEL);
+ 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;
+ struct delta_au au = *pau;
+ unsigned int data_offset;
+ 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.paddr += data_offset;
+ au.vaddr += data_offset;
+
+ ret = delta_mjpeg_ipc_decode(pctx, &au);
+ 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,
+};
diff --git a/drivers/media/platform/sti/delta/delta-mjpeg-fw.h b/drivers/media/platform/sti/delta/delta-mjpeg-fw.h
new file mode 100644
index 000000000000..de803d0c2fe8
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mjpeg-fw.h
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-mjpeg-hdr.c b/drivers/media/platform/sti/delta/delta-mjpeg-hdr.c
new file mode 100644
index 000000000000..a8fd8fa0ecb5
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mjpeg-hdr.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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;
+}
diff --git a/drivers/media/platform/sti/delta/delta-mjpeg.h b/drivers/media/platform/sti/delta/delta-mjpeg.h
new file mode 100644
index 000000000000..18e6b37217ee
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-mjpeg.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 */
diff --git a/drivers/media/platform/sti/delta/delta-v4l2.c b/drivers/media/platform/sti/delta/delta-v4l2.c
new file mode 100644
index 000000000000..c6f2e244b7a8
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta-v4l2.c
@@ -0,0 +1,1993 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Hugues Fruchet <hugues.fruchet@st.com>
+ * Jean-Christophe Trotin <jean-christophe.trotin@st.com>
+ * for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "delta.h"
+#include "delta-debug.h"
+#include "delta-ipc.h"
+
+#define DELTA_NAME "st-delta"
+
+#define DELTA_PREFIX "[---:----]"
+
+#define to_ctx(__fh) container_of(__fh, struct delta_ctx, fh)
+#define to_au(__vbuf) container_of(__vbuf, struct delta_au, vbuf)
+#define to_frame(__vbuf) container_of(__vbuf, struct delta_frame, vbuf)
+
+#define call_dec_op(dec, op, args...)\
+ ((dec && (dec)->op) ? (dec)->op(args) : 0)
+
+/* registry of available decoders */
+static const struct delta_dec *delta_decoders[] = {
+#ifdef CONFIG_VIDEO_STI_DELTA_MJPEG
+ &mjpegdec,
+#endif
+};
+
+static inline int frame_size(u32 w, u32 h, u32 fmt)
+{
+ switch (fmt) {
+ case V4L2_PIX_FMT_NV12:
+ return (w * h * 3) / 2;
+ default:
+ return 0;
+ }
+}
+
+static inline int frame_stride(u32 w, u32 fmt)
+{
+ switch (fmt) {
+ case V4L2_PIX_FMT_NV12:
+ return w;
+ default:
+ return 0;
+ }
+}
+
+static void dump_au(struct delta_ctx *ctx, struct delta_au *au)
+{
+ struct delta_dev *delta = ctx->dev;
+ u32 size = 10; /* dump first & last 10 bytes */
+ u8 *data = (u8 *)(au->vaddr);
+
+ if (au->size <= (size * 2))
+ dev_dbg(delta->dev, "%s dump au[%d] dts=%lld size=%d data=%*ph\n",
+ ctx->name, au->vbuf.vb2_buf.index, au->dts, au->size,
+ au->size, data);
+ else
+ dev_dbg(delta->dev, "%s dump au[%d] dts=%lld size=%d data=%*ph..%*ph\n",
+ ctx->name, au->vbuf.vb2_buf.index, au->dts, au->size,
+ size, data, size, data + au->size - size);
+}
+
+static void dump_frame(struct delta_ctx *ctx, struct delta_frame *frame)
+{
+ struct delta_dev *delta = ctx->dev;
+ u32 size = 10; /* dump first 10 bytes */
+ u8 *data = (u8 *)(frame->vaddr);
+
+ dev_dbg(delta->dev, "%s dump frame[%d] dts=%lld type=%s field=%s data=%*ph\n",
+ ctx->name, frame->index, frame->dts,
+ frame_type_str(frame->flags),
+ frame_field_str(frame->field),
+ size, data);
+}
+
+static void delta_au_done(struct delta_ctx *ctx, struct delta_au *au, int err)
+{
+ struct vb2_v4l2_buffer *vbuf;
+
+ vbuf = &au->vbuf;
+ vbuf->sequence = ctx->au_num++;
+ v4l2_m2m_buf_done(vbuf, err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+}
+
+static void delta_frame_done(struct delta_ctx *ctx, struct delta_frame *frame,
+ int err)
+{
+ struct vb2_v4l2_buffer *vbuf;
+
+ dump_frame(ctx, frame);
+
+ /* decoded frame is now output to user */
+ frame->state |= DELTA_FRAME_OUT;
+
+ vbuf = &frame->vbuf;
+ vbuf->sequence = ctx->frame_num++;
+ v4l2_m2m_buf_done(vbuf, err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+
+ if (frame->info.size) /* ignore EOS */
+ ctx->output_frames++;
+}
+
+static void requeue_free_frames(struct delta_ctx *ctx)
+{
+ struct vb2_v4l2_buffer *vbuf;
+ struct delta_frame *frame;
+ unsigned int i;
+
+ /* requeue all free frames */
+ for (i = 0; i < ctx->nb_of_frames; i++) {
+ frame = ctx->frames[i];
+ if (frame->state == DELTA_FRAME_FREE) {
+ vbuf = &frame->vbuf;
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+ frame->state = DELTA_FRAME_M2M;
+ }
+ }
+}
+
+static int delta_recycle(struct delta_ctx *ctx, struct delta_frame *frame)
+{
+ const struct delta_dec *dec = ctx->dec;
+
+ /* recycle frame on decoder side */
+ call_dec_op(dec, recycle, ctx, frame);
+
+ /* this frame is no more output */
+ frame->state &= ~DELTA_FRAME_OUT;
+
+ /* requeue free frame */
+ if (frame->state == DELTA_FRAME_FREE) {
+ struct vb2_v4l2_buffer *vbuf = &frame->vbuf;
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+ frame->state = DELTA_FRAME_M2M;
+ }
+
+ /* reset other frame fields */
+ frame->flags = 0;
+ frame->dts = 0;
+
+ return 0;
+}
+
+static void delta_push_dts(struct delta_ctx *ctx, u64 val)
+{
+ struct delta_dts *dts;
+
+ dts = kzalloc(sizeof(*dts), GFP_KERNEL);
+ if (!dts)
+ return;
+
+ INIT_LIST_HEAD(&dts->list);
+
+ /*
+ * protected by global lock acquired
+ * by V4L2 when calling delta_vb2_au_queue
+ */
+ dts->val = val;
+ list_add_tail(&dts->list, &ctx->dts);
+}
+
+static void delta_pop_dts(struct delta_ctx *ctx, u64 *val)
+{
+ struct delta_dev *delta = ctx->dev;
+ struct delta_dts *dts;
+
+ /*
+ * protected by global lock acquired
+ * by V4L2 when calling delta_vb2_au_queue
+ */
+ if (list_empty(&ctx->dts)) {
+ dev_warn(delta->dev, "%s no dts to pop ... output dts = 0\n",
+ ctx->name);
+ *val = 0;
+ return;
+ }
+
+ dts = list_first_entry(&ctx->dts, struct delta_dts, list);
+ list_del(&dts->list);
+
+ *val = dts->val;
+
+ kfree(dts);
+}
+
+static void delta_flush_dts(struct delta_ctx *ctx)
+{
+ struct delta_dts *dts;
+ struct delta_dts *next;
+
+ /*
+ * protected by global lock acquired
+ * by V4L2 when calling delta_vb2_au_queue
+ */
+
+ /* free all pending dts */
+ list_for_each_entry_safe(dts, next, &ctx->dts, list)
+ kfree(dts);
+
+ /* reset list */
+ INIT_LIST_HEAD(&ctx->dts);
+}
+
+static inline int frame_alignment(u32 fmt)
+{
+ switch (fmt) {
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ /* multiple of 2 */
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+static inline int estimated_au_size(u32 w, u32 h)
+{
+ /*
+ * for a MJPEG stream encoded from YUV422 pixel format,
+ * assuming a compression ratio of 2, the maximum size
+ * of an access unit is (width x height x 2) / 2,
+ * so (width x height)
+ */
+ return (w * h);
+}
+
+static void set_default_params(struct delta_ctx *ctx)
+{
+ struct delta_frameinfo *frameinfo = &ctx->frameinfo;
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+
+ memset(frameinfo, 0, sizeof(*frameinfo));
+ frameinfo->pixelformat = V4L2_PIX_FMT_NV12;
+ frameinfo->width = DELTA_DEFAULT_WIDTH;
+ frameinfo->height = DELTA_DEFAULT_HEIGHT;
+ frameinfo->aligned_width = ALIGN(frameinfo->width,
+ DELTA_WIDTH_ALIGNMENT);
+ frameinfo->aligned_height = ALIGN(frameinfo->height,
+ DELTA_HEIGHT_ALIGNMENT);
+ frameinfo->size = frame_size(frameinfo->aligned_width,
+ frameinfo->aligned_height,
+ frameinfo->pixelformat);
+ frameinfo->field = V4L2_FIELD_NONE;
+ frameinfo->colorspace = V4L2_COLORSPACE_REC709;
+ frameinfo->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ frameinfo->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ frameinfo->quantization = V4L2_QUANTIZATION_DEFAULT;
+
+ memset(streaminfo, 0, sizeof(*streaminfo));
+ streaminfo->streamformat = DELTA_DEFAULT_STREAMFORMAT;
+ streaminfo->width = DELTA_DEFAULT_WIDTH;
+ streaminfo->height = DELTA_DEFAULT_HEIGHT;
+ streaminfo->field = V4L2_FIELD_NONE;
+ streaminfo->colorspace = V4L2_COLORSPACE_REC709;
+ streaminfo->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ streaminfo->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ streaminfo->quantization = V4L2_QUANTIZATION_DEFAULT;
+
+ ctx->max_au_size = estimated_au_size(streaminfo->width,
+ streaminfo->height);
+}
+
+static const struct delta_dec *delta_find_decoder(struct delta_ctx *ctx,
+ u32 streamformat,
+ u32 pixelformat)
+{
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec;
+ unsigned int i;
+
+ for (i = 0; i < delta->nb_of_decoders; i++) {
+ dec = delta->decoders[i];
+ if ((dec->pixelformat == pixelformat) &&
+ (dec->streamformat == streamformat))
+ return dec;
+ }
+
+ return NULL;
+}
+
+static void register_format(u32 format, u32 formats[], u32 *nb_of_formats)
+{
+ u32 i;
+
+ for (i = 0; i < *nb_of_formats; i++) {
+ if (format == formats[i])
+ return;
+ }
+
+ formats[(*nb_of_formats)++] = format;
+}
+
+static void register_formats(struct delta_dev *delta)
+{
+ unsigned int i;
+
+ for (i = 0; i < delta->nb_of_decoders; i++) {
+ register_format(delta->decoders[i]->pixelformat,
+ delta->pixelformats,
+ &delta->nb_of_pixelformats);
+
+ register_format(delta->decoders[i]->streamformat,
+ delta->streamformats,
+ &delta->nb_of_streamformats);
+ }
+}
+
+static void register_decoders(struct delta_dev *delta)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(delta_decoders); i++) {
+ if (delta->nb_of_decoders >= DELTA_MAX_DECODERS) {
+ dev_dbg(delta->dev,
+ "%s failed to register %s decoder (%d maximum reached)\n",
+ DELTA_PREFIX, delta_decoders[i]->name,
+ DELTA_MAX_DECODERS);
+ return;
+ }
+
+ delta->decoders[delta->nb_of_decoders++] = delta_decoders[i];
+ dev_info(delta->dev, "%s %s decoder registered\n",
+ DELTA_PREFIX, delta_decoders[i]->name);
+ }
+}
+
+static void delta_lock(void *priv)
+{
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+
+ mutex_lock(&delta->lock);
+}
+
+static void delta_unlock(void *priv)
+{
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+
+ mutex_unlock(&delta->lock);
+}
+
+static int delta_open_decoder(struct delta_ctx *ctx, u32 streamformat,
+ u32 pixelformat, const struct delta_dec **pdec)
+{
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec;
+ int ret;
+
+ dec = delta_find_decoder(ctx, streamformat, ctx->frameinfo.pixelformat);
+ if (!dec) {
+ dev_err(delta->dev, "%s no decoder found matching %4.4s => %4.4s\n",
+ ctx->name, (char *)&streamformat, (char *)&pixelformat);
+ return -EINVAL;
+ }
+
+ dev_dbg(delta->dev, "%s one decoder matching %4.4s => %4.4s\n",
+ ctx->name, (char *)&streamformat, (char *)&pixelformat);
+
+ /* update instance name */
+ snprintf(ctx->name, sizeof(ctx->name), "[%3d:%4.4s]",
+ delta->instance_id, (char *)&streamformat);
+
+ /* open decoder instance */
+ ret = call_dec_op(dec, open, ctx);
+ if (ret) {
+ dev_err(delta->dev, "%s failed to open decoder instance (%d)\n",
+ ctx->name, ret);
+ return ret;
+ }
+
+ dev_dbg(delta->dev, "%s %s decoder opened\n", ctx->name, dec->name);
+
+ *pdec = dec;
+
+ return ret;
+}
+
+/*
+ * V4L2 ioctl operations
+ */
+
+static int delta_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+
+ strlcpy(cap->driver, DELTA_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, delta->vdev->name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ delta->pdev->name);
+
+ return 0;
+}
+
+static int delta_enum_fmt_stream(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+
+ if (unlikely(f->index >= delta->nb_of_streamformats))
+ return -EINVAL;
+
+ f->pixelformat = delta->streamformats[f->index];
+
+ return 0;
+}
+
+static int delta_enum_fmt_frame(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+
+ if (unlikely(f->index >= delta->nb_of_pixelformats))
+ return -EINVAL;
+
+ f->pixelformat = delta->pixelformats[f->index];
+
+ return 0;
+}
+
+static int delta_g_fmt_stream(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+ unsigned char str[100] = "";
+
+ if (!(ctx->flags & DELTA_FLAG_STREAMINFO))
+ dev_dbg(delta->dev,
+ "%s V4L2 GET_FMT (OUTPUT): no stream information available, default to %s\n",
+ ctx->name,
+ delta_streaminfo_str(streaminfo, str, sizeof(str)));
+
+ pix->pixelformat = streaminfo->streamformat;
+ pix->width = streaminfo->width;
+ pix->height = streaminfo->height;
+ pix->field = streaminfo->field;
+ pix->bytesperline = 0;
+ pix->sizeimage = ctx->max_au_size;
+ pix->colorspace = streaminfo->colorspace;
+ pix->xfer_func = streaminfo->xfer_func;
+ pix->ycbcr_enc = streaminfo->ycbcr_enc;
+ pix->quantization = streaminfo->quantization;
+
+ return 0;
+}
+
+static int delta_g_fmt_frame(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct delta_frameinfo *frameinfo = &ctx->frameinfo;
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+ unsigned char str[100] = "";
+
+ if (!(ctx->flags & DELTA_FLAG_FRAMEINFO))
+ dev_dbg(delta->dev,
+ "%s V4L2 GET_FMT (CAPTURE): no frame information available, default to %s\n",
+ ctx->name,
+ delta_frameinfo_str(frameinfo, str, sizeof(str)));
+
+ pix->pixelformat = frameinfo->pixelformat;
+ pix->width = frameinfo->aligned_width;
+ pix->height = frameinfo->aligned_height;
+ pix->field = frameinfo->field;
+ pix->bytesperline = frame_stride(frameinfo->aligned_width,
+ frameinfo->pixelformat);
+ pix->sizeimage = frameinfo->size;
+
+ if (ctx->flags & DELTA_FLAG_STREAMINFO) {
+ /* align colorspace & friends on stream ones if any set */
+ frameinfo->colorspace = streaminfo->colorspace;
+ frameinfo->xfer_func = streaminfo->xfer_func;
+ frameinfo->ycbcr_enc = streaminfo->ycbcr_enc;
+ frameinfo->quantization = streaminfo->quantization;
+ }
+ pix->colorspace = frameinfo->colorspace;
+ pix->xfer_func = frameinfo->xfer_func;
+ pix->ycbcr_enc = frameinfo->ycbcr_enc;
+ pix->quantization = frameinfo->quantization;
+
+ return 0;
+}
+
+static int delta_try_fmt_stream(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ u32 streamformat = pix->pixelformat;
+ const struct delta_dec *dec;
+ u32 width, height;
+ u32 au_size;
+
+ dec = delta_find_decoder(ctx, streamformat, ctx->frameinfo.pixelformat);
+ if (!dec) {
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (OUTPUT): unsupported format %4.4s\n",
+ ctx->name, (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ /* adjust width & height */
+ width = pix->width;
+ height = pix->height;
+ v4l_bound_align_image
+ (&pix->width,
+ DELTA_MIN_WIDTH,
+ dec->max_width ? dec->max_width : DELTA_MAX_WIDTH,
+ 0,
+ &pix->height,
+ DELTA_MIN_HEIGHT,
+ dec->max_height ? dec->max_height : DELTA_MAX_HEIGHT,
+ 0, 0);
+
+ if ((pix->width != width) || (pix->height != height))
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (OUTPUT): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n",
+ ctx->name, width, height,
+ pix->width, pix->height);
+
+ au_size = estimated_au_size(pix->width, pix->height);
+ if (pix->sizeimage < au_size) {
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (OUTPUT): size updated %d -> %d to fit estimated size\n",
+ ctx->name, pix->sizeimage, au_size);
+ pix->sizeimage = au_size;
+ }
+
+ pix->bytesperline = 0;
+
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int delta_try_fmt_frame(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ u32 pixelformat = pix->pixelformat;
+ const struct delta_dec *dec;
+ u32 width, height;
+
+ dec = delta_find_decoder(ctx, ctx->streaminfo.streamformat,
+ pixelformat);
+ if (!dec) {
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (CAPTURE): unsupported format %4.4s\n",
+ ctx->name, (char *)&pixelformat);
+ return -EINVAL;
+ }
+
+ /* adjust width & height */
+ width = pix->width;
+ height = pix->height;
+ v4l_bound_align_image(&pix->width,
+ DELTA_MIN_WIDTH, DELTA_MAX_WIDTH,
+ frame_alignment(pixelformat) - 1,
+ &pix->height,
+ DELTA_MIN_HEIGHT, DELTA_MAX_HEIGHT,
+ frame_alignment(pixelformat) - 1, 0);
+
+ if ((pix->width != width) || (pix->height != height))
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n",
+ ctx->name, width, height, pix->width, pix->height);
+
+ /* default decoder alignment constraint */
+ width = ALIGN(pix->width, DELTA_WIDTH_ALIGNMENT);
+ height = ALIGN(pix->height, DELTA_HEIGHT_ALIGNMENT);
+ if ((pix->width != width) || (pix->height != height))
+ dev_dbg(delta->dev,
+ "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit decoder alignment\n",
+ ctx->name, width, height, pix->width, pix->height);
+
+ if (!pix->colorspace) {
+ pix->colorspace = V4L2_COLORSPACE_REC709;
+ pix->xfer_func = V4L2_XFER_FUNC_DEFAULT;
+ pix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ pix->quantization = V4L2_QUANTIZATION_DEFAULT;
+ }
+
+ pix->width = width;
+ pix->height = height;
+ pix->bytesperline = frame_stride(pix->width, pixelformat);
+ pix->sizeimage = frame_size(pix->width, pix->height, pixelformat);
+
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int delta_s_fmt_stream(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ struct vb2_queue *vq;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int ret;
+
+ ret = delta_try_fmt_stream(file, fh, f);
+ if (ret) {
+ dev_dbg(delta->dev,
+ "%s V4L2 S_FMT (OUTPUT): unsupported format %4.4s\n",
+ ctx->name, (char *)&pix->pixelformat);
+ return ret;
+ }
+
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+ if (vb2_is_streaming(vq)) {
+ dev_dbg(delta->dev, "%s V4L2 S_FMT (OUTPUT): queue busy\n",
+ ctx->name);
+ return -EBUSY;
+ }
+
+ ctx->max_au_size = pix->sizeimage;
+ ctx->streaminfo.width = pix->width;
+ ctx->streaminfo.height = pix->height;
+ ctx->streaminfo.streamformat = pix->pixelformat;
+ ctx->streaminfo.colorspace = pix->colorspace;
+ ctx->streaminfo.xfer_func = pix->xfer_func;
+ ctx->streaminfo.ycbcr_enc = pix->ycbcr_enc;
+ ctx->streaminfo.quantization = pix->quantization;
+ ctx->flags |= DELTA_FLAG_STREAMINFO;
+
+ return 0;
+}
+
+static int delta_s_fmt_frame(struct file *file, void *fh, struct v4l2_format *f)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec = ctx->dec;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct delta_frameinfo frameinfo;
+ unsigned char str[100] = "";
+ struct vb2_queue *vq;
+ int ret;
+
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+ if (vb2_is_streaming(vq)) {
+ dev_dbg(delta->dev, "%s V4L2 S_FMT (CAPTURE): queue busy\n",
+ ctx->name);
+ return -EBUSY;
+ }
+
+ if (ctx->state < DELTA_STATE_READY) {
+ /*
+ * decoder not yet opened and valid stream header not found,
+ * could not negotiate format with decoder, check at least
+ * pixel format & negotiate resolution boundaries
+ * and alignment...
+ */
+ ret = delta_try_fmt_frame(file, fh, f);
+ if (ret) {
+ dev_dbg(delta->dev,
+ "%s V4L2 S_FMT (CAPTURE): unsupported format %4.4s\n",
+ ctx->name, (char *)&pix->pixelformat);
+ return ret;
+ }
+
+ return 0;
+ }
+
+ /* set frame information to decoder */
+ memset(&frameinfo, 0, sizeof(frameinfo));
+ frameinfo.pixelformat = pix->pixelformat;
+ frameinfo.width = pix->width;
+ frameinfo.height = pix->height;
+ frameinfo.aligned_width = pix->width;
+ frameinfo.aligned_height = pix->height;
+ frameinfo.size = pix->sizeimage;
+ frameinfo.field = pix->field;
+ frameinfo.colorspace = pix->colorspace;
+ frameinfo.xfer_func = pix->xfer_func;
+ frameinfo.ycbcr_enc = pix->ycbcr_enc;
+ frameinfo.quantization = pix->quantization;
+ ret = call_dec_op(dec, set_frameinfo, ctx, &frameinfo);
+ if (ret)
+ return ret;
+
+ /* then get what decoder can really do */
+ ret = call_dec_op(dec, get_frameinfo, ctx, &frameinfo);
+ if (ret)
+ return ret;
+
+ ctx->flags |= DELTA_FLAG_FRAMEINFO;
+ ctx->frameinfo = frameinfo;
+ dev_dbg(delta->dev,
+ "%s V4L2 SET_FMT (CAPTURE): frameinfo updated to %s\n",
+ ctx->name,
+ delta_frameinfo_str(&frameinfo, str, sizeof(str)));
+
+ pix->pixelformat = frameinfo.pixelformat;
+ pix->width = frameinfo.aligned_width;
+ pix->height = frameinfo.aligned_height;
+ pix->bytesperline = frame_stride(pix->width, pix->pixelformat);
+ pix->sizeimage = frameinfo.size;
+ pix->field = frameinfo.field;
+ pix->colorspace = frameinfo.colorspace;
+ pix->xfer_func = frameinfo.xfer_func;
+ pix->ycbcr_enc = frameinfo.ycbcr_enc;
+ pix->quantization = frameinfo.quantization;
+
+ return 0;
+}
+
+static int delta_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct delta_ctx *ctx = to_ctx(fh);
+ struct delta_frameinfo *frameinfo = &ctx->frameinfo;
+ struct v4l2_rect crop;
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if ((ctx->flags & DELTA_FLAG_FRAMEINFO) &&
+ (frameinfo->flags & DELTA_FRAMEINFO_FLAG_CROP)) {
+ crop = frameinfo->crop;
+ } else {
+ /* default to video dimensions */
+ crop.left = 0;
+ crop.top = 0;
+ crop.width = frameinfo->width;
+ crop.height = frameinfo->height;
+ }
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ /* visible area inside video */
+ s->r = crop;
+ break;
+ case V4L2_SEL_TGT_COMPOSE_PADDED:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ /* up to aligned dimensions */
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = frameinfo->aligned_width;
+ s->r.height = frameinfo->aligned_height;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void delta_complete_eos(struct delta_ctx *ctx,
+ struct delta_frame *frame)
+{
+ struct delta_dev *delta = ctx->dev;
+ const struct v4l2_event ev = {.type = V4L2_EVENT_EOS};
+
+ /*
+ * Send EOS to user:
+ * - by returning an empty frame flagged to V4L2_BUF_FLAG_LAST
+ * - and then send EOS event
+ */
+
+ /* empty frame */
+ frame->info.size = 0;
+
+ /* set the last buffer flag */
+ frame->flags |= V4L2_BUF_FLAG_LAST;
+
+ /* release frame to user */
+ delta_frame_done(ctx, frame, 0);
+
+ /* send EOS event */
+ v4l2_event_queue_fh(&ctx->fh, &ev);
+
+ dev_dbg(delta->dev, "%s EOS completed\n", ctx->name);
+}
+
+static int delta_try_decoder_cmd(struct file *file, void *fh,
+ struct v4l2_decoder_cmd *cmd)
+{
+ if (cmd->cmd != V4L2_DEC_CMD_STOP)
+ return -EINVAL;
+
+ if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK)
+ return -EINVAL;
+
+ if (!(cmd->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) &&
+ (cmd->stop.pts != 0))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int delta_decoder_stop_cmd(struct delta_ctx *ctx, void *fh)
+{
+ const struct delta_dec *dec = ctx->dec;
+ struct delta_dev *delta = ctx->dev;
+ struct delta_frame *frame = NULL;
+ int ret = 0;
+
+ dev_dbg(delta->dev, "%s EOS received\n", ctx->name);
+
+ if (ctx->state != DELTA_STATE_READY)
+ return 0;
+
+ /* drain the decoder */
+ call_dec_op(dec, drain, ctx);
+
+ /* release to user drained frames */
+ while (1) {
+ frame = NULL;
+ ret = call_dec_op(dec, get_frame, ctx, &frame);
+ if (ret == -ENODATA) {
+ /* no more decoded frames */
+ break;
+ }
+ if (frame) {
+ dev_dbg(delta->dev, "%s drain frame[%d]\n",
+ ctx->name, frame->index);
+
+ /* pop timestamp and mark frame with it */
+ delta_pop_dts(ctx, &frame->dts);
+
+ /* release decoded frame to user */
+ delta_frame_done(ctx, frame, 0);
+ }
+ }
+
+ /* try to complete EOS */
+ ret = delta_get_free_frame(ctx, &frame);
+ if (ret)
+ goto delay_eos;
+
+ /* new frame available, EOS can now be completed */
+ delta_complete_eos(ctx, frame);
+
+ ctx->state = DELTA_STATE_EOS;
+
+ return 0;
+
+delay_eos:
+ /*
+ * EOS completion from driver is delayed because
+ * we don't have a free empty frame available.
+ * EOS completion is so delayed till next frame_queue() call
+ * to be sure to have a free empty frame available.
+ */
+ ctx->state = DELTA_STATE_WF_EOS;
+ dev_dbg(delta->dev, "%s EOS delayed\n", ctx->name);
+
+ return 0;
+}
+
+static int delta_decoder_cmd(struct file *file, void *fh,
+ struct v4l2_decoder_cmd *cmd)
+{
+ struct delta_ctx *ctx = to_ctx(fh);
+ int ret = 0;
+
+ ret = delta_try_decoder_cmd(file, fh, cmd);
+ if (ret)
+ return ret;
+
+ return delta_decoder_stop_cmd(ctx, fh);
+}
+
+static int delta_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_EOS:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* v4l2 ioctl ops */
+static const struct v4l2_ioctl_ops delta_ioctl_ops = {
+ .vidioc_querycap = delta_querycap,
+ .vidioc_enum_fmt_vid_cap = delta_enum_fmt_frame,
+ .vidioc_g_fmt_vid_cap = delta_g_fmt_frame,
+ .vidioc_try_fmt_vid_cap = delta_try_fmt_frame,
+ .vidioc_s_fmt_vid_cap = delta_s_fmt_frame,
+ .vidioc_enum_fmt_vid_out = delta_enum_fmt_stream,
+ .vidioc_g_fmt_vid_out = delta_g_fmt_stream,
+ .vidioc_try_fmt_vid_out = delta_try_fmt_stream,
+ .vidioc_s_fmt_vid_out = delta_s_fmt_stream,
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_g_selection = delta_g_selection,
+ .vidioc_try_decoder_cmd = delta_try_decoder_cmd,
+ .vidioc_decoder_cmd = delta_decoder_cmd,
+ .vidioc_subscribe_event = delta_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/*
+ * mem-to-mem operations
+ */
+
+static void delta_run_work(struct work_struct *work)
+{
+ struct delta_ctx *ctx = container_of(work, struct delta_ctx, run_work);
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec = ctx->dec;
+ struct delta_au *au;
+ struct delta_frame *frame = NULL;
+ int ret = 0;
+ bool discard = false;
+ struct vb2_v4l2_buffer *vbuf;
+
+ if (!dec) {
+ dev_err(delta->dev, "%s no decoder opened yet\n", ctx->name);
+ return;
+ }
+
+ /* protect instance against reentrancy */
+ mutex_lock(&ctx->lock);
+
+ vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ if (!vbuf) {
+ dev_err(delta->dev, "%s no buffer to decode\n", ctx->name);
+ mutex_unlock(&ctx->lock);
+ return;
+ }
+ au = to_au(vbuf);
+ au->size = vb2_get_plane_payload(&vbuf->vb2_buf, 0);
+ au->dts = vbuf->vb2_buf.timestamp;
+
+ /* dump access unit */
+ dump_au(ctx, au);
+
+ /* enable the hardware */
+ if (!dec->pm) {
+ ret = delta_get_sync(ctx);
+ if (ret)
+ goto err;
+ }
+
+ /* decode this access unit */
+ ret = call_dec_op(dec, decode, ctx, au);
+
+ /*
+ * if the (-ENODATA) value is returned, it refers to the interlaced
+ * stream case for which 2 access units are needed to get 1 frame.
+ * So, this returned value doesn't mean that the decoding fails, but
+ * indicates that the timestamp information of the access unit shall
+ * not be taken into account, and that the V4L2 buffer associated with
+ * the access unit shall be flagged with V4L2_BUF_FLAG_ERROR to inform
+ * the user of this situation
+ */
+ if (ret == -ENODATA) {
+ discard = true;
+ } else if (ret) {
+ dev_err(delta->dev, "%s decoding failed (%d)\n",
+ ctx->name, ret);
+
+ /* disable the hardware */
+ if (!dec->pm)
+ delta_put_autosuspend(ctx);
+
+ goto err;
+ }
+
+ /* disable the hardware */
+ if (!dec->pm)
+ delta_put_autosuspend(ctx);
+
+ /* push au timestamp in FIFO */
+ if (!discard)
+ delta_push_dts(ctx, au->dts);
+
+ /* get available decoded frames */
+ while (1) {
+ ret = call_dec_op(dec, get_frame, ctx, &frame);
+ if (ret == -ENODATA) {
+ /* no more decoded frames */
+ goto out;
+ }
+ if (ret) {
+ dev_err(delta->dev, "%s cannot get decoded frame (%d)\n",
+ ctx->name, ret);
+ goto out;
+ }
+ if (!frame) {
+ dev_err(delta->dev,
+ "%s NULL decoded frame\n",
+ ctx->name);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* pop timestamp and mark frame with it */
+ delta_pop_dts(ctx, &frame->dts);
+
+ /* release decoded frame to user */
+ delta_frame_done(ctx, frame, 0);
+ }
+
+out:
+ requeue_free_frames(ctx);
+ delta_au_done(ctx, au, (discard ? -ENODATA : 0));
+ mutex_unlock(&ctx->lock);
+ v4l2_m2m_job_finish(delta->m2m_dev, ctx->fh.m2m_ctx);
+ return;
+
+err:
+ requeue_free_frames(ctx);
+ delta_au_done(ctx, au, ret);
+ mutex_unlock(&ctx->lock);
+ v4l2_m2m_job_finish(delta->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static void delta_device_run(void *priv)
+{
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+
+ queue_work(delta->work_queue, &ctx->run_work);
+}
+
+static void delta_job_abort(void *priv)
+{
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+
+ dev_dbg(delta->dev, "%s aborting job\n", ctx->name);
+
+ ctx->aborting = true;
+}
+
+static int delta_job_ready(void *priv)
+{
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+ int src_bufs = v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx);
+
+ if (!src_bufs) {
+ dev_dbg(delta->dev, "%s not ready: not enough video buffers.\n",
+ ctx->name);
+ return 0;
+ }
+
+ if (!v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) {
+ dev_dbg(delta->dev, "%s not ready: not enough video capture buffers.\n",
+ ctx->name);
+ return 0;
+ }
+
+ if (ctx->aborting) {
+ dev_dbg(delta->dev, "%s job not ready: aborting\n", ctx->name);
+ return 0;
+ }
+
+ dev_dbg(delta->dev, "%s job ready\n", ctx->name);
+
+ return 1;
+}
+
+/* mem-to-mem ops */
+static struct v4l2_m2m_ops delta_m2m_ops = {
+ .device_run = delta_device_run,
+ .job_ready = delta_job_ready,
+ .job_abort = delta_job_abort,
+ .lock = delta_lock,
+ .unlock = delta_unlock,
+};
+
+/*
+ * VB2 queue operations
+ */
+
+static int delta_vb2_au_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct delta_ctx *ctx = vb2_get_drv_priv(vq);
+ unsigned int size = ctx->max_au_size;
+
+ if (*num_planes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *num_planes = 1;
+ if (*num_buffers < 1)
+ *num_buffers = 1;
+ if (*num_buffers > DELTA_MAX_AUS)
+ *num_buffers = DELTA_MAX_AUS;
+
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int delta_vb2_au_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct delta_dev *delta = ctx->dev;
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct delta_au *au = to_au(vbuf);
+
+ if (!au->prepared) {
+ /* get memory addresses */
+ au->vaddr = vb2_plane_vaddr(&au->vbuf.vb2_buf, 0);
+ au->paddr = vb2_dma_contig_plane_dma_addr
+ (&au->vbuf.vb2_buf, 0);
+ au->prepared = true;
+ dev_dbg(delta->dev, "%s au[%d] prepared; virt=0x%p, phy=0x%pad\n",
+ ctx->name, vb->index, au->vaddr, &au->paddr);
+ }
+
+ if (vbuf->field == V4L2_FIELD_ANY)
+ vbuf->field = V4L2_FIELD_NONE;
+
+ return 0;
+}
+
+static int delta_setup_frame(struct delta_ctx *ctx,
+ struct delta_frame *frame)
+{
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec = ctx->dec;
+
+ if (frame->index >= DELTA_MAX_FRAMES) {
+ dev_err(delta->dev,
+ "%s frame index=%d exceeds output frame count (%d)\n",
+ ctx->name, frame->index, DELTA_MAX_FRAMES);
+ return -EINVAL;
+ }
+
+ if (ctx->nb_of_frames >= DELTA_MAX_FRAMES) {
+ dev_err(delta->dev,
+ "%s number of frames exceeds output frame count (%d > %d)\n",
+ ctx->name, ctx->nb_of_frames, DELTA_MAX_FRAMES);
+ return -EINVAL;
+ }
+
+ if (frame->index != ctx->nb_of_frames) {
+ dev_warn(delta->dev,
+ "%s frame index discontinuity detected, expected %d, got %d\n",
+ ctx->name, ctx->nb_of_frames, frame->index);
+ }
+
+ frame->state = DELTA_FRAME_FREE;
+ ctx->frames[ctx->nb_of_frames] = frame;
+ ctx->nb_of_frames++;
+
+ /* setup frame on decoder side */
+ return call_dec_op(dec, setup_frame, ctx, frame);
+}
+
+/*
+ * default implementation of get_frameinfo decoder ops
+ * matching frame information from stream information
+ * & with default pixel format & default alignment.
+ */
+int delta_get_frameinfo_default(struct delta_ctx *ctx,
+ struct delta_frameinfo *frameinfo)
+{
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+
+ memset(frameinfo, 0, sizeof(*frameinfo));
+ frameinfo->pixelformat = V4L2_PIX_FMT_NV12;
+ frameinfo->width = streaminfo->width;
+ frameinfo->height = streaminfo->height;
+ frameinfo->aligned_width = ALIGN(streaminfo->width,
+ DELTA_WIDTH_ALIGNMENT);
+ frameinfo->aligned_height = ALIGN(streaminfo->height,
+ DELTA_HEIGHT_ALIGNMENT);
+ frameinfo->size = frame_size(frameinfo->aligned_width,
+ frameinfo->aligned_height,
+ frameinfo->pixelformat);
+ if (streaminfo->flags & DELTA_STREAMINFO_FLAG_CROP) {
+ frameinfo->flags |= DELTA_FRAMEINFO_FLAG_CROP;
+ frameinfo->crop = streaminfo->crop;
+ }
+ if (streaminfo->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT) {
+ frameinfo->flags |= DELTA_FRAMEINFO_FLAG_PIXELASPECT;
+ frameinfo->pixelaspect = streaminfo->pixelaspect;
+ }
+ frameinfo->field = streaminfo->field;
+
+ return 0;
+}
+
+/*
+ * default implementation of recycle decoder ops
+ * consisting to relax the "decoded" frame state
+ */
+int delta_recycle_default(struct delta_ctx *pctx,
+ struct delta_frame *frame)
+{
+ frame->state &= ~DELTA_FRAME_DEC;
+
+ return 0;
+}
+
+static void dump_frames_status(struct delta_ctx *ctx)
+{
+ struct delta_dev *delta = ctx->dev;
+ unsigned int i;
+ struct delta_frame *frame;
+ unsigned char str[100] = "";
+
+ dev_info(delta->dev,
+ "%s dumping frames status...\n", ctx->name);
+
+ for (i = 0; i < ctx->nb_of_frames; i++) {
+ frame = ctx->frames[i];
+ dev_info(delta->dev,
+ "%s frame[%d] %s\n",
+ ctx->name, frame->index,
+ frame_state_str(frame->state,
+ str, sizeof(str)));
+ }
+}
+
+int delta_get_free_frame(struct delta_ctx *ctx,
+ struct delta_frame **pframe)
+{
+ struct delta_dev *delta = ctx->dev;
+ struct vb2_v4l2_buffer *vbuf;
+ struct delta_frame *frame;
+
+ *pframe = NULL;
+
+ vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+ if (!vbuf) {
+ dev_err(delta->dev, "%s no frame available",
+ ctx->name);
+ return -EIO;
+ }
+
+ frame = to_frame(vbuf);
+ frame->state &= ~DELTA_FRAME_M2M;
+ if (frame->state != DELTA_FRAME_FREE) {
+ dev_err(delta->dev,
+ "%s frame[%d] is not free\n",
+ ctx->name, frame->index);
+ dump_frames_status(ctx);
+ return -ENODATA;
+ }
+
+ dev_dbg(delta->dev,
+ "%s get free frame[%d]\n", ctx->name, frame->index);
+
+ *pframe = frame;
+ return 0;
+}
+
+int delta_get_sync(struct delta_ctx *ctx)
+{
+ struct delta_dev *delta = ctx->dev;
+ int ret = 0;
+
+ /* enable the hardware */
+ ret = pm_runtime_get_sync(delta->dev);
+ if (ret < 0) {
+ dev_err(delta->dev, "%s pm_runtime_get_sync failed (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void delta_put_autosuspend(struct delta_ctx *ctx)
+{
+ struct delta_dev *delta = ctx->dev;
+
+ pm_runtime_put_autosuspend(delta->dev);
+}
+
+static void delta_vb2_au_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int delta_vb2_au_start_streaming(struct vb2_queue *q,
+ unsigned int count)
+{
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec = ctx->dec;
+ struct delta_au *au;
+ int ret = 0;
+ struct vb2_v4l2_buffer *vbuf = NULL;
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+ struct delta_frameinfo *frameinfo = &ctx->frameinfo;
+ unsigned char str1[100] = "";
+ unsigned char str2[100] = "";
+
+ if ((ctx->state != DELTA_STATE_WF_FORMAT) &&
+ (ctx->state != DELTA_STATE_WF_STREAMINFO))
+ return 0;
+
+ if (ctx->state == DELTA_STATE_WF_FORMAT) {
+ /* open decoder if not yet done */
+ ret = delta_open_decoder(ctx,
+ ctx->streaminfo.streamformat,
+ ctx->frameinfo.pixelformat, &dec);
+ if (ret)
+ goto err;
+ ctx->dec = dec;
+ ctx->state = DELTA_STATE_WF_STREAMINFO;
+ }
+
+ /*
+ * first buffer should contain stream header,
+ * decode it to get the infos related to stream
+ * such as width, height, dpb, ...
+ */
+ vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ if (!vbuf) {
+ dev_err(delta->dev, "%s failed to start streaming, no stream header buffer enqueued\n",
+ ctx->name);
+ ret = -EINVAL;
+ goto err;
+ }
+ au = to_au(vbuf);
+ au->size = vb2_get_plane_payload(&vbuf->vb2_buf, 0);
+ au->dts = vbuf->vb2_buf.timestamp;
+
+ delta_push_dts(ctx, au->dts);
+
+ /* dump access unit */
+ dump_au(ctx, au);
+
+ /* decode this access unit */
+ ret = call_dec_op(dec, decode, ctx, au);
+ if (ret) {
+ dev_err(delta->dev, "%s failed to start streaming, header decoding failed (%d)\n",
+ ctx->name, ret);
+ goto err;
+ }
+
+ ret = call_dec_op(dec, get_streaminfo, ctx, streaminfo);
+ if (ret) {
+ dev_dbg_ratelimited(delta->dev,
+ "%s failed to start streaming, valid stream header not yet decoded\n",
+ ctx->name);
+ goto err;
+ }
+ ctx->flags |= DELTA_FLAG_STREAMINFO;
+
+ ret = call_dec_op(dec, get_frameinfo, ctx, frameinfo);
+ if (ret)
+ goto err;
+ ctx->flags |= DELTA_FLAG_FRAMEINFO;
+
+ ctx->state = DELTA_STATE_READY;
+
+ dev_dbg(delta->dev, "%s %s => %s\n", ctx->name,
+ delta_streaminfo_str(streaminfo, str1, sizeof(str1)),
+ delta_frameinfo_str(frameinfo, str2, sizeof(str2)));
+
+ delta_au_done(ctx, au, ret);
+ return 0;
+
+err:
+ /*
+ * return all buffers to vb2 in QUEUED state.
+ * This will give ownership back to userspace
+ */
+ if (vbuf)
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED);
+
+ while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED);
+ return ret;
+}
+
+static void delta_vb2_au_stop_streaming(struct vb2_queue *q)
+{
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *vbuf;
+
+ delta_flush_dts(ctx);
+
+ /* return all buffers to vb2 in ERROR state */
+ while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+
+ ctx->au_num = 0;
+
+ ctx->aborting = false;
+}
+
+static int delta_vb2_frame_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct delta_ctx *ctx = vb2_get_drv_priv(vq);
+ struct delta_dev *delta = ctx->dev;
+ struct delta_streaminfo *streaminfo = &ctx->streaminfo;
+ struct delta_frameinfo *frameinfo = &ctx->frameinfo;
+ unsigned int size = frameinfo->size;
+
+ /*
+ * the number of output buffers needed for decoding =
+ * user need (*num_buffers given, usually for display pipeline) +
+ * stream need (streaminfo->dpb) +
+ * decoding peak smoothing (depends on DELTA IP perf)
+ */
+ if (*num_buffers < DELTA_MIN_FRAME_USER) {
+ dev_dbg(delta->dev,
+ "%s num_buffers too low (%d), increasing to %d\n",
+ ctx->name, *num_buffers, DELTA_MIN_FRAME_USER);
+ *num_buffers = DELTA_MIN_FRAME_USER;
+ }
+
+ *num_buffers += streaminfo->dpb + DELTA_PEAK_FRAME_SMOOTHING;
+
+ if (*num_buffers > DELTA_MAX_FRAMES) {
+ dev_dbg(delta->dev,
+ "%s output frame count too high (%d), cut to %d\n",
+ ctx->name, *num_buffers, DELTA_MAX_FRAMES);
+ *num_buffers = DELTA_MAX_FRAMES;
+ }
+
+ if (*num_planes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ /* single plane for Y and CbCr */
+ *num_planes = 1;
+
+ sizes[0] = size;
+
+ ctx->nb_of_frames = 0;
+
+ return 0;
+}
+
+static int delta_vb2_frame_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct delta_dev *delta = ctx->dev;
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct delta_frame *frame = to_frame(vbuf);
+ int ret = 0;
+
+ if (!frame->prepared) {
+ frame->index = vbuf->vb2_buf.index;
+ frame->vaddr = vb2_plane_vaddr(&vbuf->vb2_buf, 0);
+ frame->paddr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0);
+ frame->info = ctx->frameinfo;
+
+ ret = delta_setup_frame(ctx, frame);
+ if (ret) {
+ dev_err(delta->dev,
+ "%s setup_frame() failed (%d)\n",
+ ctx->name, ret);
+ return ret;
+ }
+ frame->prepared = true;
+ dev_dbg(delta->dev,
+ "%s frame[%d] prepared; virt=0x%p, phy=0x%pad\n",
+ ctx->name, vb->index, frame->vaddr,
+ &frame->paddr);
+ }
+
+ frame->flags = vbuf->flags;
+
+ return 0;
+}
+
+static void delta_vb2_frame_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct delta_frame *frame = to_frame(vbuf);
+
+ /* update V4L2 fields for user */
+ vb2_set_plane_payload(&vbuf->vb2_buf, 0, frame->info.size);
+ vb->timestamp = frame->dts;
+ vbuf->field = frame->field;
+ vbuf->flags = frame->flags;
+}
+
+static void delta_vb2_frame_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *q = vb->vb2_queue;
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct delta_frame *frame = to_frame(vbuf);
+
+ if (ctx->state == DELTA_STATE_WF_EOS) {
+ /* new frame available, EOS can now be completed */
+ delta_complete_eos(ctx, frame);
+
+ ctx->state = DELTA_STATE_EOS;
+
+ /* return, no need to recycle this buffer to decoder */
+ return;
+ }
+
+ /* recycle this frame */
+ delta_recycle(ctx, frame);
+}
+
+static void delta_vb2_frame_stop_streaming(struct vb2_queue *q)
+{
+ struct delta_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *vbuf;
+ struct delta_frame *frame;
+ const struct delta_dec *dec = ctx->dec;
+ unsigned int i;
+
+ delta_flush_dts(ctx);
+
+ call_dec_op(dec, flush, ctx);
+
+ /*
+ * return all buffers to vb2 in ERROR state
+ * & reset each frame state to OUT
+ */
+ for (i = 0; i < ctx->nb_of_frames; i++) {
+ frame = ctx->frames[i];
+ if (!(frame->state & DELTA_FRAME_OUT)) {
+ vbuf = &frame->vbuf;
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+ }
+ frame->state = DELTA_FRAME_OUT;
+ }
+
+ ctx->frame_num = 0;
+
+ ctx->aborting = false;
+}
+
+/* VB2 queue ops */
+static struct vb2_ops delta_vb2_au_ops = {
+ .queue_setup = delta_vb2_au_queue_setup,
+ .buf_prepare = delta_vb2_au_prepare,
+ .buf_queue = delta_vb2_au_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = delta_vb2_au_start_streaming,
+ .stop_streaming = delta_vb2_au_stop_streaming,
+};
+
+static struct vb2_ops delta_vb2_frame_ops = {
+ .queue_setup = delta_vb2_frame_queue_setup,
+ .buf_prepare = delta_vb2_frame_prepare,
+ .buf_finish = delta_vb2_frame_finish,
+ .buf_queue = delta_vb2_frame_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = delta_vb2_frame_stop_streaming,
+};
+
+/*
+ * V4L2 file operations
+ */
+
+static int queue_init(void *priv,
+ struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
+{
+ struct vb2_queue *q;
+ struct delta_ctx *ctx = priv;
+ struct delta_dev *delta = ctx->dev;
+ int ret;
+
+ /* setup vb2 queue for stream input */
+ q = src_vq;
+ q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = ctx;
+ /* overload vb2 buf with private au struct */
+ q->buf_struct_size = sizeof(struct delta_au);
+ q->ops = &delta_vb2_au_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->lock = &delta->lock;
+ q->dev = delta->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret)
+ return ret;
+
+ /* setup vb2 queue for frame output */
+ q = dst_vq;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = ctx;
+ /* overload vb2 buf with private frame struct */
+ q->buf_struct_size = sizeof(struct delta_frame)
+ + DELTA_MAX_FRAME_PRIV_SIZE;
+ q->ops = &delta_vb2_frame_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ q->lock = &delta->lock;
+ q->dev = delta->dev;
+
+ return vb2_queue_init(q);
+}
+
+static int delta_open(struct file *file)
+{
+ struct delta_dev *delta = video_drvdata(file);
+ struct delta_ctx *ctx = NULL;
+ int ret = 0;
+
+ mutex_lock(&delta->lock);
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ctx->dev = delta;
+
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+
+ INIT_WORK(&ctx->run_work, delta_run_work);
+ mutex_init(&ctx->lock);
+
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(delta->m2m_dev, ctx,
+ queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ dev_err(delta->dev, "%s failed to initialize m2m context (%d)\n",
+ DELTA_PREFIX, ret);
+ goto err_fh_del;
+ }
+
+ /*
+ * wait stream format to determine which
+ * decoder to open
+ */
+ ctx->state = DELTA_STATE_WF_FORMAT;
+
+ INIT_LIST_HEAD(&ctx->dts);
+
+ /* set the instance name */
+ delta->instance_id++;
+ snprintf(ctx->name, sizeof(ctx->name), "[%3d:----]",
+ delta->instance_id);
+
+ /* default parameters for frame and stream */
+ set_default_params(ctx);
+
+ /* enable ST231 clocks */
+ if (delta->clk_st231)
+ if (clk_prepare_enable(delta->clk_st231))
+ dev_warn(delta->dev, "failed to enable st231 clk\n");
+
+ /* enable FLASH_PROMIP clock */
+ if (delta->clk_flash_promip)
+ if (clk_prepare_enable(delta->clk_flash_promip))
+ dev_warn(delta->dev, "failed to enable delta promip clk\n");
+
+ mutex_unlock(&delta->lock);
+
+ dev_dbg(delta->dev, "%s decoder instance created\n", ctx->name);
+
+ return 0;
+
+err_fh_del:
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+err:
+ mutex_unlock(&delta->lock);
+
+ return ret;
+}
+
+static int delta_release(struct file *file)
+{
+ struct delta_ctx *ctx = to_ctx(file->private_data);
+ struct delta_dev *delta = ctx->dev;
+ const struct delta_dec *dec = ctx->dec;
+
+ mutex_lock(&delta->lock);
+
+ /* close decoder */
+ call_dec_op(dec, close, ctx);
+
+ /*
+ * trace a summary of instance
+ * before closing (debug purpose)
+ */
+ delta_trace_summary(ctx);
+
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+
+ /* disable ST231 clocks */
+ if (delta->clk_st231)
+ clk_disable_unprepare(delta->clk_st231);
+
+ /* disable FLASH_PROMIP clock */
+ if (delta->clk_flash_promip)
+ clk_disable_unprepare(delta->clk_flash_promip);
+
+ dev_dbg(delta->dev, "%s decoder instance released\n", ctx->name);
+
+ kfree(ctx);
+
+ mutex_unlock(&delta->lock);
+ return 0;
+}
+
+/* V4L2 file ops */
+static const struct v4l2_file_operations delta_fops = {
+ .owner = THIS_MODULE,
+ .open = delta_open,
+ .release = delta_release,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+ .poll = v4l2_m2m_fop_poll,
+};
+
+/*
+ * Platform device operations
+ */
+
+static int delta_register_device(struct delta_dev *delta)
+{
+ int ret;
+ struct video_device *vdev;
+
+ if (!delta)
+ return -ENODEV;
+
+ delta->m2m_dev = v4l2_m2m_init(&delta_m2m_ops);
+ if (IS_ERR(delta->m2m_dev)) {
+ dev_err(delta->dev, "%s failed to initialize v4l2-m2m device\n",
+ DELTA_PREFIX);
+ ret = PTR_ERR(delta->m2m_dev);
+ goto err;
+ }
+
+ vdev = video_device_alloc();
+ if (!vdev) {
+ dev_err(delta->dev, "%s failed to allocate video device\n",
+ DELTA_PREFIX);
+ ret = -ENOMEM;
+ goto err_m2m_release;
+ }
+
+ vdev->fops = &delta_fops;
+ vdev->ioctl_ops = &delta_ioctl_ops;
+ vdev->release = video_device_release;
+ vdev->lock = &delta->lock;
+ vdev->vfl_dir = VFL_DIR_M2M;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M;
+ vdev->v4l2_dev = &delta->v4l2_dev;
+ snprintf(vdev->name, sizeof(vdev->name), "%s-%s",
+ DELTA_NAME, DELTA_FW_VERSION);
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret) {
+ dev_err(delta->dev, "%s failed to register video device\n",
+ DELTA_PREFIX);
+ goto err_vdev_release;
+ }
+
+ delta->vdev = vdev;
+ video_set_drvdata(vdev, delta);
+ return 0;
+
+err_vdev_release:
+ video_device_release(vdev);
+err_m2m_release:
+ v4l2_m2m_release(delta->m2m_dev);
+err:
+ return ret;
+}
+
+static void delta_unregister_device(struct delta_dev *delta)
+{
+ if (!delta)
+ return;
+
+ if (delta->m2m_dev)
+ v4l2_m2m_release(delta->m2m_dev);
+
+ video_unregister_device(delta->vdev);
+}
+
+static int delta_probe(struct platform_device *pdev)
+{
+ struct delta_dev *delta;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ delta = devm_kzalloc(dev, sizeof(*delta), GFP_KERNEL);
+ if (!delta) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ delta->dev = dev;
+ delta->pdev = pdev;
+ platform_set_drvdata(pdev, delta);
+
+ mutex_init(&delta->lock);
+
+ /* get clock resources */
+ delta->clk_delta = devm_clk_get(dev, "delta");
+ if (IS_ERR(delta->clk_delta)) {
+ dev_dbg(dev, "%s can't get delta clock\n", DELTA_PREFIX);
+ delta->clk_delta = NULL;
+ }
+
+ delta->clk_st231 = devm_clk_get(dev, "delta-st231");
+ if (IS_ERR(delta->clk_st231)) {
+ dev_dbg(dev, "%s can't get delta-st231 clock\n", DELTA_PREFIX);
+ delta->clk_st231 = NULL;
+ }
+
+ delta->clk_flash_promip = devm_clk_get(dev, "delta-flash-promip");
+ if (IS_ERR(delta->clk_flash_promip)) {
+ dev_dbg(dev, "%s can't get delta-flash-promip clock\n",
+ DELTA_PREFIX);
+ delta->clk_flash_promip = NULL;
+ }
+
+ /* init pm_runtime used for power management */
+ pm_runtime_set_autosuspend_delay(dev, DELTA_HW_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_enable(dev);
+
+ /* init firmware ipc channel */
+ ret = delta_ipc_init(delta);
+ if (ret) {
+ dev_err(delta->dev, "%s failed to initialize firmware ipc channel\n",
+ DELTA_PREFIX);
+ goto err;
+ }
+
+ /* register all available decoders */
+ register_decoders(delta);
+
+ /* register all supported formats */
+ register_formats(delta);
+
+ /* register on V4L2 */
+ ret = v4l2_device_register(dev, &delta->v4l2_dev);
+ if (ret) {
+ dev_err(delta->dev, "%s failed to register V4L2 device\n",
+ DELTA_PREFIX);
+ goto err;
+ }
+
+ delta->work_queue = create_workqueue(DELTA_NAME);
+ if (!delta->work_queue) {
+ dev_err(delta->dev, "%s failed to allocate work queue\n",
+ DELTA_PREFIX);
+ ret = -ENOMEM;
+ goto err_v4l2;
+ }
+
+ /* register device */
+ ret = delta_register_device(delta);
+ if (ret)
+ goto err_work_queue;
+
+ dev_info(dev, "%s %s registered as /dev/video%d\n",
+ DELTA_PREFIX, delta->vdev->name, delta->vdev->num);
+
+ return 0;
+
+err_work_queue:
+ destroy_workqueue(delta->work_queue);
+err_v4l2:
+ v4l2_device_unregister(&delta->v4l2_dev);
+err:
+ return ret;
+}
+
+static int delta_remove(struct platform_device *pdev)
+{
+ struct delta_dev *delta = platform_get_drvdata(pdev);
+
+ delta_ipc_exit(delta);
+
+ delta_unregister_device(delta);
+
+ destroy_workqueue(delta->work_queue);
+
+ pm_runtime_put_autosuspend(delta->dev);
+ pm_runtime_disable(delta->dev);
+
+ v4l2_device_unregister(&delta->v4l2_dev);
+
+ return 0;
+}
+
+static int delta_runtime_suspend(struct device *dev)
+{
+ struct delta_dev *delta = dev_get_drvdata(dev);
+
+ if (delta->clk_delta)
+ clk_disable_unprepare(delta->clk_delta);
+
+ return 0;
+}
+
+static int delta_runtime_resume(struct device *dev)
+{
+ struct delta_dev *delta = dev_get_drvdata(dev);
+
+ if (delta->clk_delta)
+ if (clk_prepare_enable(delta->clk_delta))
+ dev_warn(dev, "failed to prepare/enable delta clk\n");
+
+ return 0;
+}
+
+/* PM ops */
+static const struct dev_pm_ops delta_pm_ops = {
+ .runtime_suspend = delta_runtime_suspend,
+ .runtime_resume = delta_runtime_resume,
+};
+
+static const struct of_device_id delta_match_types[] = {
+ {
+ .compatible = "st,st-delta",
+ },
+ {
+ /* end node */
+ }
+};
+
+MODULE_DEVICE_TABLE(of, delta_match_types);
+
+static struct platform_driver delta_driver = {
+ .probe = delta_probe,
+ .remove = delta_remove,
+ .driver = {
+ .name = DELTA_NAME,
+ .of_match_table = delta_match_types,
+ .pm = &delta_pm_ops},
+};
+
+module_platform_driver(delta_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics DELTA video decoder V4L2 driver");
diff --git a/drivers/media/platform/sti/delta/delta.h b/drivers/media/platform/sti/delta/delta.h
new file mode 100644
index 000000000000..60c073246a01
--- /dev/null
+++ b/drivers/media/platform/sti/delta/delta.h
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Author: Hugues Fruchet <hugues.fruchet@st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 bistream 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 */
diff --git a/drivers/media/platform/sti/hva/Makefile b/drivers/media/platform/sti/hva/Makefile
index ffb69cebaef3..e3ebe968472d 100644
--- a/drivers/media/platform/sti/hva/Makefile
+++ b/drivers/media/platform/sti/hva/Makefile
@@ -1,2 +1,3 @@
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
diff --git a/drivers/media/platform/sti/hva/hva-debugfs.c b/drivers/media/platform/sti/hva/hva-debugfs.c
new file mode 100644
index 000000000000..83a6258a155b
--- /dev/null
+++ b/drivers/media/platform/sti/hva/hva-debugfs.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Yannick Fertre <yannick.fertre@st.com>
+ * Hugues Fruchet <hugues.fruchet@st.com>
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#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 hva_dbg_device(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 hva_dbg_encoders(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 hva_dbg_last(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 hva_dbg_regs(struct seq_file *s, void *data)
+{
+ struct hva_dev *hva = s->private;
+
+ hva_hw_dump_regs(hva, s);
+
+ return 0;
+}
+
+#define hva_dbg_declare(name) \
+ static int hva_dbg_##name##_open(struct inode *i, struct file *f) \
+ { \
+ return single_open(f, hva_dbg_##name, i->i_private); \
+ } \
+ static const struct file_operations hva_dbg_##name##_fops = { \
+ .open = hva_dbg_##name##_open, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+ }
+
+#define hva_dbg_create_entry(name) \
+ debugfs_create_file(#name, 0444, hva->dbg.debugfs_entry, hva, \
+ &hva_dbg_##name##_fops)
+
+hva_dbg_declare(device);
+hva_dbg_declare(encoders);
+hva_dbg_declare(last);
+hva_dbg_declare(regs);
+
+void hva_debugfs_create(struct hva_dev *hva)
+{
+ hva->dbg.debugfs_entry = debugfs_create_dir(HVA_NAME, NULL);
+ if (!hva->dbg.debugfs_entry)
+ goto err;
+
+ if (!hva_dbg_create_entry(device))
+ goto err;
+
+ if (!hva_dbg_create_entry(encoders))
+ goto err;
+
+ if (!hva_dbg_create_entry(last))
+ goto err;
+
+ if (!hva_dbg_create_entry(regs))
+ goto err;
+
+ return;
+
+err:
+ hva_debugfs_remove(hva);
+}
+
+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 hva_dbg_ctx(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;
+}
+
+hva_dbg_declare(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, &hva_dbg_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);
+}
diff --git a/drivers/media/platform/sti/hva/hva-h264.c b/drivers/media/platform/sti/hva/hva-h264.c
index 8cc8467c0cd3..e6f247a983c7 100644
--- a/drivers/media/platform/sti/hva/hva-h264.c
+++ b/drivers/media/platform/sti/hva/hva-h264.c
@@ -607,6 +607,7 @@ static int hva_h264_prepare_task(struct hva_ctx *pctx,
"%s width(%d) or height(%d) exceeds limits (%dx%d)\n",
pctx->name, frame_width, frame_height,
H264_MAX_SIZE_W, H264_MAX_SIZE_H);
+ pctx->frame_errors++;
return -EINVAL;
}
@@ -717,6 +718,7 @@ static int hva_h264_prepare_task(struct hva_ctx *pctx,
default:
dev_err(dev, "%s invalid source pixel format\n",
pctx->name);
+ pctx->frame_errors++;
return -EINVAL;
}
@@ -741,6 +743,7 @@ static int hva_h264_prepare_task(struct hva_ctx *pctx,
if (td->framerate_den == 0) {
dev_err(dev, "%s invalid framerate\n", pctx->name);
+ pctx->frame_errors++;
return -EINVAL;
}
@@ -831,6 +834,7 @@ static int hva_h264_prepare_task(struct hva_ctx *pctx,
(payload > MAX_SPS_PPS_SIZE)) {
dev_err(dev, "%s invalid sps/pps size %d\n", pctx->name,
payload);
+ pctx->frame_errors++;
return -EINVAL;
}
@@ -842,6 +846,7 @@ static int hva_h264_prepare_task(struct hva_ctx *pctx,
(u8 *)stream->vaddr,
&payload)) {
dev_err(dev, "%s fail to get SEI nal\n", pctx->name);
+ pctx->frame_errors++;
return -EINVAL;
}
@@ -963,6 +968,7 @@ err_seq_info:
err_ctx:
devm_kfree(dev, ctx);
err:
+ pctx->sys_errors++;
return ret;
}
diff --git a/drivers/media/platform/sti/hva/hva-hw.c b/drivers/media/platform/sti/hva/hva-hw.c
index 68d625b412b6..ec25bdcfa3d1 100644
--- a/drivers/media/platform/sti/hva/hva-hw.c
+++ b/drivers/media/platform/sti/hva/hva-hw.c
@@ -9,6 +9,9 @@
#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"
@@ -470,6 +473,7 @@ int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
if (pm_runtime_get_sync(dev) < 0) {
dev_err(dev, "%s failed to get pm_runtime\n", ctx->name);
+ ctx->sys_errors++;
ret = -EFAULT;
goto out;
}
@@ -481,6 +485,7 @@ int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
break;
default:
dev_dbg(dev, "%s unknown command 0x%x\n", ctx->name, cmd);
+ ctx->encode_errors++;
ret = -EFAULT;
goto out;
}
@@ -511,6 +516,7 @@ int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
msecs_to_jiffies(2000))) {
dev_err(dev, "%s %s: time out on completion\n", ctx->name,
__func__);
+ ctx->encode_errors++;
ret = -EFAULT;
goto out;
}
@@ -518,6 +524,8 @@ int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd,
/* 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);
@@ -536,3 +544,43 @@ out:
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_get_sync(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
diff --git a/drivers/media/platform/sti/hva/hva-hw.h b/drivers/media/platform/sti/hva/hva-hw.h
index efb45b927524..b46017dcfae9 100644
--- a/drivers/media/platform/sti/hva/hva-hw.h
+++ b/drivers/media/platform/sti/hva/hva-hw.h
@@ -38,5 +38,8 @@ 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 */
diff --git a/drivers/media/platform/sti/hva/hva-mem.c b/drivers/media/platform/sti/hva/hva-mem.c
index 649f66007ad6..821c78ed208c 100644
--- a/drivers/media/platform/sti/hva/hva-mem.c
+++ b/drivers/media/platform/sti/hva/hva-mem.c
@@ -17,14 +17,17 @@ int hva_mem_alloc(struct hva_ctx *ctx, u32 size, const char *name,
void *base;
b = devm_kzalloc(dev, sizeof(*b), GFP_KERNEL);
- if (!b)
+ if (!b) {
+ ctx->sys_errors++;
return -ENOMEM;
+ }
base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL | GFP_DMA,
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;
}
diff --git a/drivers/media/platform/sti/hva/hva-v4l2.c b/drivers/media/platform/sti/hva/hva-v4l2.c
index 6bf3c8588230..1c4fc33cbcb5 100644
--- a/drivers/media/platform/sti/hva/hva-v4l2.c
+++ b/drivers/media/platform/sti/hva/hva-v4l2.c
@@ -15,8 +15,6 @@
#include "hva.h"
#include "hva-hw.h"
-#define HVA_NAME "st-hva"
-
#define MIN_FRAMES 1
#define MIN_STREAMS 1
@@ -226,6 +224,28 @@ static int hva_open_encoder(struct hva_ctx *ctx, u32 streamformat,
return ret;
}
+static void hva_dbg_summary(struct hva_ctx *ctx)
+{
+ struct device *dev = ctx_to_dev(ctx);
+ struct hva_streaminfo *stream = &ctx->streaminfo;
+ struct hva_frameinfo *frame = &ctx->frameinfo;
+
+ if (!(ctx->flags & HVA_FLAG_STREAMINFO))
+ return;
+
+ dev_dbg(dev, "%s %4.4s %dx%d > %4.4s %dx%d %s %s: %d frames encoded, %d system errors, %d encoding errors, %d frame errors\n",
+ ctx->name,
+ (char *)&frame->pixelformat,
+ frame->aligned_width, frame->aligned_height,
+ (char *)&stream->streamformat,
+ stream->width, stream->height,
+ stream->profile, stream->level,
+ ctx->encoded_frames,
+ ctx->sys_errors,
+ ctx->encode_errors,
+ ctx->frame_errors);
+}
+
/*
* V4L2 ioctl operations
*/
@@ -614,19 +634,17 @@ static int hva_s_ctrl(struct v4l2_ctrl *ctrl)
break;
case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
ctx->ctrls.profile = ctrl->val;
- if (ctx->flags & HVA_FLAG_STREAMINFO)
- snprintf(ctx->streaminfo.profile,
- sizeof(ctx->streaminfo.profile),
- "%s profile",
- v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]);
+ snprintf(ctx->streaminfo.profile,
+ sizeof(ctx->streaminfo.profile),
+ "%s profile",
+ v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]);
break;
case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
ctx->ctrls.level = ctrl->val;
- if (ctx->flags & HVA_FLAG_STREAMINFO)
- snprintf(ctx->streaminfo.level,
- sizeof(ctx->streaminfo.level),
- "level %s",
- v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]);
+ snprintf(ctx->streaminfo.level,
+ sizeof(ctx->streaminfo.level),
+ "level %s",
+ v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]);
break;
case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
ctx->ctrls.entropy_mode = ctrl->val;
@@ -793,6 +811,10 @@ static void hva_run_work(struct work_struct *work)
/* protect instance against reentrancy */
mutex_lock(&ctx->lock);
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_dbg_perf_begin(ctx);
+#endif
+
src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
@@ -812,6 +834,12 @@ static void hva_run_work(struct work_struct *work)
dst_buf->field = V4L2_FIELD_NONE;
dst_buf->sequence = ctx->stream_num - 1;
+ ctx->encoded_frames++;
+
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_dbg_perf_end(ctx, stream);
+#endif
+
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
}
@@ -1026,6 +1054,8 @@ err:
v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED);
}
+ ctx->sys_errors++;
+
return ret;
}
@@ -1150,6 +1180,7 @@ static int hva_open(struct file *file)
if (ret) {
dev_err(dev, "%s [x:x] failed to setup controls\n",
HVA_PREFIX);
+ ctx->sys_errors++;
goto err_fh;
}
ctx->fh.ctrl_handler = &ctx->ctrl_handler;
@@ -1162,6 +1193,7 @@ static int hva_open(struct file *file)
ret = PTR_ERR(ctx->fh.m2m_ctx);
dev_err(dev, "%s failed to initialize m2m context (%d)\n",
HVA_PREFIX, ret);
+ ctx->sys_errors++;
goto err_ctrls;
}
@@ -1175,6 +1207,10 @@ static int hva_open(struct file *file)
/* default parameters for frame and stream */
set_default_params(ctx);
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_dbg_ctx_create(ctx);
+#endif
+
dev_info(dev, "%s encoder instance created\n", ctx->name);
return 0;
@@ -1206,6 +1242,9 @@ static int hva_release(struct file *file)
hva->nb_of_instances--;
}
+ /* trace a summary of instance before closing (debug purpose) */
+ hva_dbg_summary(ctx);
+
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
@@ -1213,6 +1252,10 @@ static int hva_release(struct file *file)
v4l2_fh_del(&ctx->fh);
v4l2_fh_exit(&ctx->fh);
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_dbg_ctx_remove(ctx);
+#endif
+
dev_info(dev, "%s encoder instance released\n", ctx->name);
kfree(ctx);
@@ -1337,6 +1380,10 @@ static int hva_probe(struct platform_device *pdev)
goto err_hw;
}
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_debugfs_create(hva);
+#endif
+
hva->work_queue = create_workqueue(HVA_NAME);
if (!hva->work_queue) {
dev_err(dev, "%s %s failed to allocate work queue\n",
@@ -1358,6 +1405,9 @@ static int hva_probe(struct platform_device *pdev)
err_work_queue:
destroy_workqueue(hva->work_queue);
err_v4l2:
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_debugfs_remove(hva);
+#endif
v4l2_device_unregister(&hva->v4l2_dev);
err_hw:
hva_hw_remove(hva);
@@ -1376,6 +1426,10 @@ static int hva_remove(struct platform_device *pdev)
hva_hw_remove(hva);
+#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS
+ hva_debugfs_remove(hva);
+#endif
+
v4l2_device_unregister(&hva->v4l2_dev);
dev_info(dev, "%s %s removed\n", HVA_PREFIX, pdev->name);
diff --git a/drivers/media/platform/sti/hva/hva.h b/drivers/media/platform/sti/hva/hva.h
index caa580825541..0d749b257a21 100644
--- a/drivers/media/platform/sti/hva/hva.h
+++ b/drivers/media/platform/sti/hva/hva.h
@@ -21,7 +21,8 @@
#define ctx_to_hdev(c) (c->hva_dev)
-#define HVA_PREFIX "[---:----]"
+#define HVA_NAME "st-hva"
+#define HVA_PREFIX "[---:----]"
extern const struct hva_enc nv12h264enc;
extern const struct hva_enc nv21h264enc;
@@ -153,6 +154,61 @@ 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;
@@ -182,6 +238,11 @@ struct hva_enc;
* @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;
@@ -207,11 +268,31 @@ struct hva_ctx {
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
@@ -250,6 +331,7 @@ struct hva_ctx {
* @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;
@@ -284,6 +366,9 @@ struct hva_dev {
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
};
/**
@@ -312,4 +397,13 @@ struct hva_enc {
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 */