# http://www.samsung.com
#
-obj-$(CONFIG_VIDEO_EXYNOS_SMFC) += smfc.o smfc-v4l2-ioctls.o smfc-regs.o
+obj-$(CONFIG_VIDEO_EXYNOS_SMFC) += smfc.o smfc-v4l2-ioctls.o smfc-regs.o smfc-stream-parser.o
__raw_writel(SMFC_DHT_LEN, base + REG_SEC_DHT_LEN);
}
+void smfc_hwconfigure_tables_for_decompression(struct smfc_ctx *ctx)
+{
+ void __iomem *base = ctx->smfc->reg;
+ void __iomem *qtbl_base = ctx->smfc->reg + REG_QTBL_BASE;
+ u32 tblsel = ctx->num_components << 16;
+ int i;
+
+ /* Huffman table selector configuration */
+ for (i = 0; i < ctx->num_components; i++) {
+ u32 val = (ctx->huffman_tables->compsel[i].idx_dc |
+ (ctx->huffman_tables->compsel[i].idx_ac << 1)) & 3;
+ tblsel |= val << (i * 2 + 4);
+ }
+
+ /* quantization table configuration */
+ for (i = 0; i < ctx->num_components; i++) {
+ if (ctx->quantizer_tables->compsel[i] != INVALID_QTBLIDX) {
+ u8 *table = ctx->quantizer_tables->table[i];
+ int j;
+
+ for (j = 0; j < SMFC_MCU_SIZE; j += 4) {
+ u32 quants;
+
+ quants = table[j + 0] << 0;
+ quants |= table[j + 1] << 8;
+ quants |= table[j + 2] << 16;
+ quants |= table[j + 3] << 24;
+ __raw_writel(quants,
+ qtbl_base + SMFC_MCU_SIZE * i + j);
+ }
+ /* quantization table selector */
+ tblsel |= ctx->quantizer_tables->compsel[i] << (i * 2);
+ }
+ }
+
+ /* Huffman table configuration */
+ for (i = 0; i < 4; i++) {
+ __raw_writel(ctx->huffman_tables->dc[0].code32[i],
+ base + REG_HTBL_LUMA_DCLEN + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->dc[0].value32[i],
+ base + REG_HTBL_LUMA_DCVAL + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->dc[1].code32[i],
+ base + REG_HTBL_CHROMA_DCLEN + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->dc[1].value32[i],
+ base + REG_HTBL_CHROMA_DCVAL + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->ac[0].code32[i],
+ base + REG_HTBL_LUMA_ACLEN + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->ac[1].code32[i],
+ base + REG_HTBL_CHROMA_ACLEN + i * sizeof(u32));
+ }
+
+ for (i = 0; i < (SMFC_NUM_AC_HVAL / 4); i++) {
+ __raw_writel(ctx->huffman_tables->ac[0].value32[i],
+ base + REG_HTBL_LUMA_ACVAL + i * sizeof(u32));
+ __raw_writel(ctx->huffman_tables->ac[1].value32[i],
+ base + REG_HTBL_CHROMA_ACVAL + i * sizeof(u32));
+ }
+
+ __raw_writel(tblsel, base + REG_MAIN_TABLE_SELECT);
+}
+
static void smfc_hwconfigure_image_base(struct smfc_ctx *ctx,
struct vb2_buffer *vb2buf,
bool thumbnail)
static u32 smfc_hwconfigure_jpeg_base(struct smfc_ctx *ctx,
struct vb2_buffer *vb2buf,
- bool thumbnail)
+ u32 offset, bool thumbnail)
{
dma_addr_t addr;
u32 off = thumbnail ? REG_SEC_JPEG_BASE : REG_MAIN_JPEG_BASE;
addr = vb2_dma_sg_plane_dma_addr(vb2buf, thumbnail ? 1 : 0);
+ addr += offset;
__raw_writel((u32)addr, ctx->smfc->reg + off);
return (u32)addr;
}
* secondary image stream base is not required because there is no
* MAX_COMPRESSED_SIZE register for the secondary image
*/
- smfc_hwconfigure_jpeg_base(ctx, vb2buf_jpg, true);
+ smfc_hwconfigure_jpeg_base(ctx, vb2buf_jpg, 0, true);
format = ctx->img_fmt->regcfg;
/*
struct vb2_v4l2_buffer *vb2buf_img, *vb2buf_jpg;
u32 stream_address;
u32 format = ctx->img_fmt->regcfg;
- u32 maxstreamsize;
__raw_writel(ctx->width | (ctx->height << 16),
ctx->smfc->reg + REG_MAIN_IMAGE_SIZE);
if (!(ctx->flags & SMFC_CTX_COMPRESS)) {
vb2buf_img = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
vb2buf_jpg = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ format |= smfc_get_jpeg_format(hfactor, vfactor);
} else {
vb2buf_img = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
vb2buf_jpg = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
smfc_hwconfigure_image_base(ctx, &vb2buf_img->vb2_buf, false);
__raw_writel(format, ctx->smfc->reg + REG_MAIN_IMAGE_FORMAT);
- stream_address = smfc_hwconfigure_jpeg_base(
- ctx, &vb2buf_jpg->vb2_buf, false);
- maxstreamsize = round_down(vb2_plane_size(&vb2buf_jpg->vb2_buf, 0),
- SMFC_STREAMSIZE_ALIGN);
- if (!IS_ALIGNED(stream_address, 16))
- maxstreamsize += SMFC_EXTRA_STREAMSIZE(stream_address);
-
- __raw_writel(maxstreamsize, ctx->smfc->reg + REG_MAIN_MAX_STREAM_SIZE);
+
+ stream_address = smfc_hwconfigure_jpeg_base(ctx, &vb2buf_jpg->vb2_buf,
+ ctx->offset_of_sos, false);
+ if (!(ctx->flags & SMFC_CTX_COMPRESS)) {
+ u32 streamsize = vb2_plane_size(&vb2buf_jpg->vb2_buf, 0);
+
+ streamsize -= ctx->offset_of_sos;
+ streamsize += stream_address & SMFC_ADDR_ALIGN_MASK;
+ streamsize = ALIGN(streamsize, SMFC_ADDR_ALIGN);
+ streamsize /= SMFC_ADDR_ALIGN;
+ __raw_writel(streamsize, ctx->smfc->reg + REG_MAIN_STREAM_SIZE);
+ } else {
+ u32 maxstreamsize = vb2_plane_size(&vb2buf_jpg->vb2_buf, 0);
+
+ maxstreamsize = round_down(maxstreamsize, SMFC_STREAMSIZE_ALIGN);
+ if (!IS_ALIGNED(stream_address, 16))
+ maxstreamsize += SMFC_EXTRA_STREAMSIZE(stream_address);
+
+ __raw_writel(maxstreamsize,
+ ctx->smfc->reg + REG_MAIN_MAX_STREAM_SIZE);
+ }
}
void smfc_hwconfigure_start(struct smfc_ctx *ctx,
#define _MEDIA_EXYNOS_SMFC_REGS_H_
/********** JPEG STANDARD *****************************************************/
+#define SMFC_JPEG_MARKER_LEN 2
+
#define SMFC_MCU_SIZE 64
+#define SMFC_MAX_QTBL_COUNT 4
+#define SMFC_NUM_HCODE 16
+#define SMFC_NUM_DC_HVAL 16
+#define SMFC_NUM_AC_HVAL 172
/********** H/W RESTRICTIONS **************************************************/
+#define SMFC_MAX_NUM_COMP 3
#define SMFC_MAX_WIDTH 16368U
#define SMFC_MAX_HEIGHT 16368U
#define SMFC_MIN_WIDTH 8U
--- /dev/null
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * The main source file of Samsung Exynos SMFC Driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dma-buf.h>
+#include <linux/slab.h>
+
+#include <media/videobuf2-core.h>
+
+#include "smfc.h"
+
+static bool smfc_alloc_tables(struct smfc_ctx *ctx)
+{
+ if (!ctx->quantizer_tables) {
+ ctx->quantizer_tables = kmalloc(
+ sizeof(*ctx->quantizer_tables), GFP_KERNEL);
+ if (!ctx->quantizer_tables)
+ return false;
+ }
+ memset(ctx->quantizer_tables, 0, sizeof(*ctx->quantizer_tables));
+ ctx->quantizer_tables->compsel[0] = INVALID_QTBLIDX;
+ ctx->quantizer_tables->compsel[1] = INVALID_QTBLIDX;
+ ctx->quantizer_tables->compsel[2] = INVALID_QTBLIDX;
+ ctx->quantizer_tables->compsel[3] = INVALID_QTBLIDX;
+
+ if (!ctx->huffman_tables) {
+ ctx->huffman_tables = kmalloc(
+ sizeof(*ctx->huffman_tables), GFP_KERNEL);
+ if (!ctx->huffman_tables)
+ return false;
+ }
+ memset(ctx->huffman_tables, 0, sizeof(*ctx->huffman_tables));
+
+ return true;
+}
+
+union jpeg_hword_t {
+ u16 hword;
+ u8 byte[2];
+};
+
+static int smfc_get_segment_length(struct smfc_ctx *ctx,
+ unsigned long addr, u8 marker, u16 *length)
+{
+ union jpeg_hword_t len;
+ int ret;
+
+ if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) {
+ ret = get_user(len.byte[0], (u16 *)addr++);
+ ret |= get_user(len.byte[1], (u16 *)addr);
+ } else {
+ ret = get_user(len.byte[1], (u16 *)addr++);
+ ret |= get_user(len.byte[0], (u16 *)addr);
+ }
+ if (ret) {
+ dev_err(ctx->smfc->dev,
+ "Failed to read length 0xFF%02X\n", marker);
+ return -EFAULT;
+ }
+
+ *length = len.hword;
+
+ return 0;
+}
+
+#define __get_u16(pos) \
+({ \
+ __typeof__(*(pos)) *__p = (pos); \
+ u16 __v = (*__p++ << 8) & 0xFF00; \
+ __v |= *__p++ & 0xFF; \
+ pos = __p; \
+ __v; \
+})
+
+#define __halfbytes_larger_than(val, maxval) \
+ (max((val) & 0xF, ((val) >> 4) & 0xF) > (maxval))
+
+unsigned int smfc_get_num_huffval(u8 *hufflen)
+{
+ int i;
+ unsigned int num = 0;
+
+ for (i = 0; i < SMFC_NUM_HCODE; i++)
+ num += hufflen[i];
+
+ return num;
+}
+
+static int smfc_parse_dht(struct smfc_ctx *ctx, unsigned long *cursor)
+{
+ u8 __user *pcursor = (u8 __user *)*cursor;
+ unsigned long segend;
+ int ret;
+ u16 len;
+
+ ret = smfc_get_segment_length(ctx, *cursor, 0xc4, &len);
+ if (ret)
+ return ret;
+
+ segend = *cursor + len;
+ pcursor += 2;
+
+ /* 17 : TcTh, L1...L16 */
+ while (((unsigned long)pcursor) < (segend - 17)) {
+ u8 *table;
+ unsigned int num_values;
+ u8 tcth;
+ bool dc;
+
+ ret = get_user(tcth, pcursor++);
+ if (ret) {
+ dev_err(ctx->smfc->dev, "Failed to read TcTh in DHT\n");
+ return ret;
+ } else if (__halfbytes_larger_than(tcth, 1)) {
+ dev_err(ctx->smfc->dev,
+ "Unsupported TcTh %#x in DHT\n", tcth);
+ return -EINVAL;
+ }
+
+ /* HUFFLEN */
+ dc = (((tcth >> 4) & 0xF) == 0);
+ table = dc ? ctx->huffman_tables->dc[tcth & 1].code
+ : ctx->huffman_tables->ac[tcth & 1].code;
+ ret = copy_from_user(table, pcursor, SMFC_NUM_HCODE);
+ pcursor += SMFC_NUM_HCODE;
+ if (ret) {
+ dev_err(ctx->smfc->dev,
+ "Failed to read HUFFLEN of %d,%d\n",
+ tcth >> 4, tcth & 1);
+ return ret;
+ }
+
+ num_values = smfc_get_num_huffval(table);
+ if ((dc && (num_values > SMFC_NUM_DC_HVAL)) ||
+ (!dc && (num_values > SMFC_NUM_AC_HVAL))) {
+ dev_err(ctx->smfc->dev,
+ "Too many values %u in huffman table %d,%d\n",
+ num_values, tcth >> 4, tcth & 1);
+ return -EINVAL;
+ }
+
+ if (((unsigned long)pcursor + num_values) > segend)
+ break;
+
+ /* HUFFVAL */
+ table = dc ? ctx->huffman_tables->dc[tcth & 1].value
+ : ctx->huffman_tables->ac[tcth & 1].value;
+ ret = copy_from_user(table, pcursor, num_values);
+ pcursor += num_values;
+ if (ret) {
+ dev_err(ctx->smfc->dev,
+ "Failed to read huffval of %d,%d\n",
+ tcth >> 4, tcth & 1);
+ return -EINVAL;
+ }
+ }
+
+ if ((*cursor + len) != (unsigned long)pcursor) {
+ dev_err(ctx->smfc->dev, "Incorrect DHT length %d\n", len);
+ return -EINVAL;
+ }
+
+ *cursor += len;
+
+ return 0;
+}
+
+static int smfc_parse_dqt(struct smfc_ctx *ctx, unsigned long *cursor)
+{
+ u8 __user *pcursor = (u8 __user *)*cursor;
+ unsigned long segend;
+ int ret;
+ u16 len;
+
+ ret = smfc_get_segment_length(ctx, *cursor, 0xdb, &len);
+ if (ret)
+ return ret;
+
+ segend = *cursor + len;
+ pcursor += 2;
+
+ /* 17 : TcTh, L1...L16 */
+ while ((unsigned long)pcursor < segend) {
+ u8 pqtq;
+
+ ret = get_user(pqtq, pcursor++);
+ if (ret) {
+ dev_err(ctx->smfc->dev, "Failed to read PqTq in DQT\n");
+ return ret;
+ } else if (pqtq >= SMFC_MAX_QTBL_COUNT) {
+ /* Pq should be 0, Tq should be < 4 */
+ dev_err(ctx->smfc->dev,
+ "Invalid PqTq %02xin DQT\n", pqtq);
+ return -EINVAL;
+ }
+
+ ret = copy_from_user(ctx->quantizer_tables->table[pqtq],
+ pcursor, SMFC_MCU_SIZE);
+ pcursor += SMFC_MCU_SIZE;
+ if (ret) {
+ dev_err(ctx->smfc->dev,
+ "Failed to read %dth Q-Table\n", pqtq);
+ return ret;
+ }
+ }
+
+ *cursor += len;
+
+ return 0;
+}
+
+#define SOF0_LENGTH 17 /* Lf+P+Y+X+Nf+Nf*Comp */
+static int smfc_parse_frameheader(struct smfc_ctx *ctx, unsigned long *cursor)
+{
+ u8 *pos;
+ int i, ret;
+ u8 sof0[SOF0_LENGTH];
+
+ pos = sof0;
+
+ ret = copy_from_user(sof0, (void __user *)*cursor, sizeof(sof0));
+ if (ret) {
+ dev_err(ctx->smfc->dev, "Failed to read SOF0\n");
+ return ret;
+ }
+
+ if (__get_u16(pos) != SOF0_LENGTH) {
+ dev_err(ctx->smfc->dev, "Unsupported data in SOF0\n");
+ return ret;
+ }
+
+ if (*pos != 8) { /* bits per sample */
+ dev_err(ctx->smfc->dev, "Unsupported bits per sample %d", *pos);
+ return -EINVAL;
+ }
+ pos++;
+
+ ctx->stream_height = __get_u16(pos);
+ ctx->stream_width = __get_u16(pos);
+
+ if ((*pos != 3) && (*pos != 1)) { /* Nf: number of components */
+ dev_err(ctx->smfc->dev, "Unsupported component count %d", *pos);
+ return -EINVAL;
+ }
+
+ /* ctx->num_components is not 0 if SOS appeared earlier than SOF0 */
+ if ((ctx->num_components != 0) && (ctx->num_components != *pos)) {
+ dev_err(ctx->smfc->dev,
+ "comp. count differs in Ns(%u) and Nf(%u)\n",
+ ctx->num_components, *pos);
+ return -EINVAL;
+ }
+
+ ctx->num_components = *pos;
+
+ pos++;
+
+ for (i = 0; i < ctx->num_components; i++, pos += 3) {
+ u8 h = (pos[1] >> 4) & 0xF;
+ u8 v = pos[1] & 0xF;
+
+ if ((pos[0] < 1) || (pos[0] > 4) || (pos[2] > 4)
+ || (h > 4) || (h > 4)) {
+ dev_err(ctx->smfc->dev,
+ "Invalid component data in SOF0 %02x%02x%02x",
+ pos[0], pos[1], pos[2]);
+ return -EINVAL;
+ }
+
+ if (pos[0] == 1) { /* Luma component */
+ ctx->stream_hfactor = h;
+ ctx->stream_vfactor = v;
+ } else if ((h != 1) || (v != 1)) { /* Chroma component */
+ dev_err(ctx->smfc->dev,
+ "Unsupported chroma factor %dx%d\n", h, v);
+ return -EINVAL;
+ }
+
+ ctx->quantizer_tables->compsel[pos[0] - 1] = pos[2];
+ }
+
+ *cursor += SOF0_LENGTH;
+
+ return 0;
+}
+
+#define SOS_LENGTH 12 /* Ls+Ns+Ns*Comp+Ss+Se+AhAl */
+static int smfc_parse_scanheader(struct smfc_ctx *ctx,
+ unsigned long streambase, unsigned long *cursor)
+{
+ u8 *pos;
+ int i, ret;
+ u8 sos[SOS_LENGTH];
+
+ pos = sos;
+
+ ctx->offset_of_sos = *cursor - streambase - SMFC_JPEG_MARKER_LEN;
+
+ ret = copy_from_user(sos, (void __user *)*cursor, sizeof(sos));
+ if (ret) {
+ dev_err(ctx->smfc->dev, "Failed to read SOS\n");
+ return ret;
+ }
+
+ if (__get_u16(pos) != SOS_LENGTH) {
+ dev_err(ctx->smfc->dev, "Unsupported length of SOS segment.\n");
+ return ret;
+ }
+
+ if ((*pos != 3) && (*pos != 1)) { /* Ns: number of components */
+ dev_err(ctx->smfc->dev, "Unsupported component count %d", *pos);
+ return -EINVAL;
+ }
+
+ /* ctx->num_components is not 0 if SOF0 appeared earlier than SOS */
+ if ((ctx->num_components != 0) && (ctx->num_components != *pos)) {
+ dev_err(ctx->smfc->dev,
+ "comp. count differs in Nf(%u) and Ns(%u)\n",
+ ctx->num_components, *pos);
+ return -EINVAL;
+ }
+
+ ctx->num_components = *pos;
+
+ pos++;
+
+ for (i = 0; i < ctx->num_components; i++, pos += 2) {
+ if ((pos[0] > 3) || __halfbytes_larger_than(pos[1], 1)) {
+ dev_err(ctx->smfc->dev,
+ "Invalid component %d data %02x%02x in SOS\n",
+ i, pos[0], pos[1]);
+ return -EINVAL;
+ }
+
+ ctx->huffman_tables->compsel[pos[0] - 1].idx_dc =
+ (pos[1] >> 4) & 0xF;
+ ctx->huffman_tables->compsel[pos[0] - 1].idx_ac = pos[1] & 0xF;
+ }
+
+ /*
+ * Skipping checking if all qauntizer tables and huffman tables
+ * specified in SOF0 and SOS, respectively because unspecified tables
+ * are initialized as zero and HWJPEG is capable of handling the cases
+ * during decompression.
+ */
+
+ *cursor += SOS_LENGTH;
+
+ return 0;
+}
+
+int smfc_parse_jpeg_header(struct smfc_ctx *ctx, struct vb2_buffer *vb)
+{
+ int ret;
+ union jpeg_hword_t marker;
+ u16 len;
+ unsigned long streambase = vb->planes[0].m.userptr;
+ unsigned long cursor = streambase;
+ unsigned long streamend = streambase + vb2_get_plane_payload(vb, 0);
+
+ ctx->num_components = 0;
+
+ if (!smfc_alloc_tables(ctx))
+ return -ENOMEM;
+
+ /* the buffer in vb the entire JPEG stream from SOI */
+
+ /* userptr: get_user/copy_from_user to read stream headers */
+ /* dmabuf: map the buffer in the kernelspace to read stream headers */
+
+ /* SOI */
+ ret = get_user(marker.byte[0], (u16 *)cursor++);
+ ret |= get_user(marker.byte[1], (u16 *)cursor++);
+ if (ret || (marker.byte[0] != 0xFF) || (marker.byte[1] != 0xD8)) {
+ dev_err(ctx->smfc->dev, "SOS maker is not found\n");
+ return -EINVAL;
+ }
+
+ while (cursor < (streamend - SMFC_JPEG_MARKER_LEN)) {
+ ret = get_user(marker.byte[0], (u16 *)cursor++);
+ ret |= get_user(marker.byte[1], (u16 *)cursor++);
+ if (ret) {
+ dev_err(ctx->smfc->dev, "Failed to read JPEG maker\n");
+ return -EFAULT;
+ }
+
+ if (marker.byte[0] != 0xFF) {
+ dev_err(ctx->smfc->dev, "Error found in JPEG stream\n");
+ return -EINVAL;
+ }
+
+ switch (marker.byte[1]) {
+ case 0xC4: /* DHT */
+ ret = smfc_parse_dht(ctx, &cursor);
+ if (ret)
+ return ret;
+ break;
+ case 0xDB: /* DQT */
+ ret = smfc_parse_dqt(ctx, &cursor);
+ if (ret)
+ return ret;
+ break;
+ case 0xC0: /* SOF0 */
+ ret = smfc_parse_frameheader(ctx, &cursor);
+ if (ret)
+ return ret;
+ break;
+ case 0xDA: /**** SOS - THE END OF HEADER PARSING ****/
+ return smfc_parse_scanheader(ctx, streambase, &cursor);
+ case 0xD9: /* EOI */
+ dev_err(ctx->smfc->dev,
+ "EOI found during header parsing\n");
+ return -EINVAL;
+ default: /* error checking */
+ if ((marker.byte[1] & 0xF0) == 0xC0) {
+ dev_err(ctx->smfc->dev,
+ "Unsupported marker 0xFF%02X found\n",
+ marker.byte[1]);
+ return -EINVAL;
+ }
+
+ /* Ignores all other markers */
+ ret = smfc_get_segment_length(
+ ctx, cursor, marker.byte[1], &len);
+ if (ret)
+ return ret;
+
+ cursor += len;
+ }
+ }
+
+ dev_err(ctx->smfc->dev, "SOS is not found in the stream\n");
+
+ return -EINVAL;
+}
#define V4L2_CAP_EXYNOS_JPEG_NO_STREAMBASE_ALIGN 0x2000
/* set if H/W does not have 128-bit alignment constraint for image base */
#define V4L2_CAP_EXYNOS_JPEG_NO_IMAGEBASE_ALIGN 0x4000
+/*
+ * Set if the driver requires the address of SOS marker for the start address
+ * of the JPEG stream. Unset if the driver requires the address of SOI marker
+ * for the start address of the JPEG stream even though H/W requires the address
+ * of SOS marker to decompress when the driver is able to find the address of
+ * SOS marker from the given address of SOI marker.
+ */
+#define V4L2_CAP_EXYNOS_JPEG_DECOMPRESSION_FROM_SOS 0x10000
+/* set if H/W supports for cropping during decompression */
+#define V4L2_CAP_EXYNOS_JPEG_DECOMPRESSION_CROP 0x20000
+/* set if H/W supports for downscaling(1/2, 1/4 and 1/8) during decompression */
+#define V4L2_CAP_EXYNOS_JPEG_DOWNSCALING 0x40000
/* SMFC SPECIFIC CONTROLS */
#define V4L2_CID_JPEG_SEC_COMP_QUALITY (V4L2_CID_JPEG_CLASS_BASE + 20)
cap->device_caps |= V4L2_CAP_EXYNOS_JPEG_NO_STREAMBASE_ALIGN;
cap->device_caps |= V4L2_CAP_EXYNOS_JPEG_NO_IMAGEBASE_ALIGN;
+ cap->device_caps |= V4L2_CAP_EXYNOS_JPEG_DECOMPRESSION;
+
return 0;
}
struct smfc_ctx *ctx = vb2_get_drv_priv(vq);
unsigned int i;
+ if (!(ctx->flags & SMFC_CTX_COMPRESS) && (*num_buffers > 1)) {
+ dev_info(ctx->smfc->dev,
+ "Decompression does not allow >1 buffers\n");
+ dev_info(ctx->smfc->dev, "forced buffer count to 1\n");
+ *num_buffers = 1;
+ }
+
if (smfc_is_compressed_type(ctx, vq->type)) {
/*
* SMFC is able to stop compression if the target buffer is not
{
struct smfc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
unsigned int i;
+ bool full_clean = false;
+
+ /* output buffers should have valid bytes_used */
+ if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
+ if (!!(ctx->flags & SMFC_CTX_COMPRESS)) {
+ unsigned long payload = ctx->width * ctx->height;
+
+ for (i = 0; i < ctx->img_fmt->num_buffers; i++) {
+ unsigned long planebytes = payload;
+
+ planebytes *= ctx->img_fmt->bpp_buf[i];
+ planebytes /= 8;
+ if (vb2_get_plane_payload(vb, i) < planebytes) {
+ dev_err(ctx->smfc->dev,
+ "Too small payload[%u]=%lu (req:%lu)\n",
+ i, vb2_get_plane_payload(vb, i),
+ planebytes);
+ return -EINVAL;
+ }
+ }
+ } else {
+ /* buffer contains JPEG stream to decompress */
+ int ret = smfc_parse_jpeg_header(ctx, vb);
+
+ if (ret != 0)
+ return ret;
+ }
+ } else {
+ /*
+ * capture payload of compression is configured
+ * in exynos_smfc_irq_handler().
+ */
+ if (!(ctx->flags & SMFC_CTX_COMPRESS)) {
+ unsigned long payload = ctx->width * ctx->height;
- if (!smfc_is_compressed_type(ctx, vb->vb2_queue->type)) {
- unsigned long payload = ctx->width * ctx->height;
- for (i = 0; i < ctx->img_fmt->num_buffers; i++) {
- unsigned long planebytes;
- planebytes = (payload * ctx->img_fmt->bpp_buf[i]) / 8;
- if (vb2_get_plane_payload(vb, i) < planebytes) {
- dev_err(ctx->smfc->dev,
- "Too small bytes_used[%u]=%lu (req.:%lu)\n",
- i, vb2_get_plane_payload(vb, i), planebytes);
- return -EINVAL;
+ for (i = 0; i < ctx->img_fmt->num_buffers; i++) {
+ unsigned long planebits = payload;
+
+ planebits *= ctx->img_fmt->bpp_buf[i];
+ vb2_set_plane_payload(vb, i, planebits / 8);
}
+ } else {
+ /*
+ * capture buffer of compression should be fully
+ * invalidated
+ */
+ full_clean = true;
}
}
+ /*
+ * FIXME: develop how to maintain a part of buffer
+ if (!(to_vb2_v4l2_buffer(vb)->flags & V4L2_BUF_FLAG_NO_CACHE_CLEAN))
+ return (full_clean) ?
+ vb2_ion_buf_prepare(vb) : vb2_ion_buf_prepare_exact(vb);
+ */
return 0;
}
clk_unprepare(ctx->smfc->clk_gate2);
}
+ kfree(ctx->quantizer_tables);
+ kfree(ctx->huffman_tables);
+
kfree(ctx);
return 0;
goto err_hwfc;
smfc_hwconfigure_reset(ctx->smfc);
- smfc_hwconfigure_tables(ctx, quality_factor);
- smfc_hwconfigure_image(ctx, chroma_hfactor, chroma_vfactor);
- if (!!(ctx->flags & SMFC_CTX_B2B_COMPRESS) &&
- !!(ctx->flags & SMFC_CTX_COMPRESS)) {
- smfc_hwconfigure_2nd_tables(ctx, thumb_quality_factor);
- smfc_hwconfigure_2nd_image(ctx, !!enable_hwfc);
+
+ if (!!(ctx->flags & SMFC_CTX_COMPRESS)) {
+ smfc_hwconfigure_tables(ctx, quality_factor);
+ smfc_hwconfigure_image(ctx, chroma_hfactor, chroma_vfactor);
+ if (!!(ctx->flags & SMFC_CTX_B2B_COMPRESS)) {
+ smfc_hwconfigure_2nd_tables(ctx, thumb_quality_factor);
+ smfc_hwconfigure_2nd_image(ctx, !!enable_hwfc);
+ }
+ } else {
+ if ((ctx->stream_width != ctx->width) ||
+ (ctx->stream_height != ctx->height)) {
+ dev_err(ctx->smfc->dev,
+ "Downscaling on decompression not allowed\n");
+ /* It is okay to abort after reset */
+ goto err_invalid_size;
+ }
+
+ smfc_hwconfigure_image(ctx,
+ ctx->stream_hfactor, ctx->stream_vfactor);
+ smfc_hwconfigure_tables_for_decompression(ctx);
}
spin_lock_irqsave(&ctx->smfc->flag_lock, flags);
smfc_hwconfigure_start(ctx, restart_interval, !!enable_hwfc);
return;
+err_invalid_size:
err_hwfc:
if (!IS_ERR(ctx->smfc->clk_gate)) {
clk_disable(ctx->smfc->clk_gate);
#define SMFC_CTX_COMPRESS (1 << 0)
#define SMFC_CTX_B2B_COMPRESS (1 << 1) /* valid if SMFC_CTX_COMPRESS is set */
+struct smfc_decomp_htable {
+ struct {
+ union {
+ u8 code[SMFC_NUM_HCODE];
+ u32 code32[SMFC_NUM_HCODE / 4];
+ };
+ union {
+ u8 value[SMFC_NUM_DC_HVAL];
+ u32 value32[SMFC_NUM_DC_HVAL / 4];
+ };
+ } dc[2];
+ struct {
+ union {
+ u8 code[SMFC_NUM_HCODE];
+ u32 code32[SMFC_NUM_HCODE / 4];
+ };
+ union {
+ u8 value[SMFC_NUM_AC_HVAL];
+ u32 value32[SMFC_NUM_AC_HVAL / 4];
+ };
+ } ac[2];
+
+ struct {
+ unsigned char idx_dc;
+ unsigned char idx_ac;
+ } compsel[SMFC_MAX_NUM_COMP]; /* compsel[0] for component 1 */
+};
+
+#define INVALID_QTBLIDX 0xFF
+struct smfc_decomp_qtable {
+ /* quantizers are *NOT* stored in the zig-zag scan order */
+ u8 table[SMFC_MAX_QTBL_COUNT][SMFC_MCU_SIZE];
+ char compsel[SMFC_MAX_NUM_COMP]; /* compsel[0] for component 1 */
+};
struct smfc_ctx {
struct v4l2_fh fh;
const struct smfc_image_format *img_fmt;
__u32 width;
__u32 height;
- /* JPEG chroma subsampling factors */
- unsigned char chroma_hfactor;
- unsigned char chroma_vfactor;
+
+ /* Compression settings */
+ unsigned char chroma_hfactor; /* horizontal chroma subsampling factor */
+ unsigned char chroma_vfactor; /* vertical chroma subsampling factor */
unsigned char restart_interval;
unsigned char quality_factor;
/*
__u32 thumb_height;
unsigned char thumb_quality_factor;
unsigned char enable_hwfc;
+
+ /* Decompression settings */
+ struct smfc_decomp_qtable *quantizer_tables;
+ struct smfc_decomp_htable *huffman_tables;
+ unsigned char stream_hfactor;
+ unsigned char stream_vfactor;
+ unsigned char num_components;
+ unsigned int offset_of_sos;
+ __u16 stream_width;
+ __u16 stream_height;
};
extern const struct v4l2_ioctl_ops smfc_v4l2_ioctl_ops;
int smfc_init_controls(struct smfc_dev *smfc, struct v4l2_ctrl_handler *hdlr);
+int smfc_parse_jpeg_header(struct smfc_ctx *ctx, struct vb2_buffer *vb);
+
/* H/W Configuration */
void smfc_hwconfigure_tables(struct smfc_ctx *ctx, unsigned int qfactor);
+void smfc_hwconfigure_tables_for_decompression(struct smfc_ctx *ctx);
void smfc_hwconfigure_image(struct smfc_ctx *ctx,
unsigned int hfactor, unsigned int vfactor);
void smfc_hwconfigure_start(struct smfc_ctx *ctx,