From 292a45d62d410d51c296e0c187c5786419da04b6 Mon Sep 17 00:00:00 2001 From: Cho KyongHo Date: Fri, 8 May 2015 11:28:33 +0900 Subject: [PATCH] [COMMON] media: smfc: add support for JPEG decompression HWJPEG is capable of decompression of baseline, sequaltial DCT, Huffman coded JPEG streams to an arbitrary image format that is supported by HWJPEG. HWJPEG requires the address of SOS marker to start decompression. Thus the driver should configure quantization tables, Huffman tables and chroma subsampling factors to HWJPEG separately with the JPEG stream address (address of SOS marker). It is convenient to parse a JPEG header in userspace. But the driver require the address of SOI marker to parse JPEG headers in the driver because the driver does not convice the userspace to provide correct information for decompression. Moreover, V4L2 does not consider about JPEG decompression. It does not provide interface for passing Huffman tables and quantization tables to the driver from userspace. JPEG stream can be simply decompressed by passing the address of SOI. If the given JPEG stream has error or is not supported, the userspace will get an error from the driver. Change-Id: I92ea5a1dc91e29bc4588fba8545bf8def0da0cb8 Signed-off-by: Cho KyongHo --- drivers/media/platform/exynos/smfc/Makefile | 2 +- .../media/platform/exynos/smfc/smfc-regs.c | 97 +++- .../media/platform/exynos/smfc/smfc-regs.h | 7 + .../platform/exynos/smfc/smfc-stream-parser.c | 444 ++++++++++++++++++ .../platform/exynos/smfc/smfc-v4l2-ioctls.c | 14 + drivers/media/platform/exynos/smfc/smfc.c | 98 +++- drivers/media/platform/exynos/smfc/smfc.h | 54 ++- 7 files changed, 685 insertions(+), 31 deletions(-) create mode 100644 drivers/media/platform/exynos/smfc/smfc-stream-parser.c diff --git a/drivers/media/platform/exynos/smfc/Makefile b/drivers/media/platform/exynos/smfc/Makefile index 6977f942f4a3..29807416ea05 100644 --- a/drivers/media/platform/exynos/smfc/Makefile +++ b/drivers/media/platform/exynos/smfc/Makefile @@ -3,4 +3,4 @@ # 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 diff --git a/drivers/media/platform/exynos/smfc/smfc-regs.c b/drivers/media/platform/exynos/smfc/smfc-regs.c index 97f8f2516685..d81bbab58406 100644 --- a/drivers/media/platform/exynos/smfc/smfc-regs.c +++ b/drivers/media/platform/exynos/smfc/smfc-regs.c @@ -186,6 +186,67 @@ void smfc_hwconfigure_2nd_tables(struct smfc_ctx *ctx, unsigned int qfactor) __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) @@ -218,12 +279,13 @@ static void smfc_hwconfigure_image_base(struct smfc_ctx *ctx, 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; } @@ -262,7 +324,7 @@ void smfc_hwconfigure_2nd_image(struct smfc_ctx *ctx, bool hwfc_enabled) * 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; /* @@ -285,7 +347,6 @@ void smfc_hwconfigure_image(struct smfc_ctx *ctx, 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); @@ -293,6 +354,7 @@ void smfc_hwconfigure_image(struct smfc_ctx *ctx, 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); @@ -309,14 +371,27 @@ void smfc_hwconfigure_image(struct smfc_ctx *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, diff --git a/drivers/media/platform/exynos/smfc/smfc-regs.h b/drivers/media/platform/exynos/smfc/smfc-regs.h index b5cf6c2f7667..56895ff58bcb 100644 --- a/drivers/media/platform/exynos/smfc/smfc-regs.h +++ b/drivers/media/platform/exynos/smfc/smfc-regs.h @@ -13,9 +13,16 @@ #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 diff --git a/drivers/media/platform/exynos/smfc/smfc-stream-parser.c b/drivers/media/platform/exynos/smfc/smfc-stream-parser.c new file mode 100644 index 000000000000..951056e73452 --- /dev/null +++ b/drivers/media/platform/exynos/smfc/smfc-stream-parser.c @@ -0,0 +1,444 @@ +/* + * 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 +#include +#include +#include + +#include + +#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; +} diff --git a/drivers/media/platform/exynos/smfc/smfc-v4l2-ioctls.c b/drivers/media/platform/exynos/smfc/smfc-v4l2-ioctls.c index bb86caed96ed..67401f42c650 100644 --- a/drivers/media/platform/exynos/smfc/smfc-v4l2-ioctls.c +++ b/drivers/media/platform/exynos/smfc/smfc-v4l2-ioctls.c @@ -28,6 +28,18 @@ #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) @@ -422,6 +434,8 @@ static int smfc_v4l2_querycap(struct file *filp, void *fh, 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; } diff --git a/drivers/media/platform/exynos/smfc/smfc.c b/drivers/media/platform/exynos/smfc/smfc.c index e279aa5ac732..5ad94b3a6588 100644 --- a/drivers/media/platform/exynos/smfc/smfc.c +++ b/drivers/media/platform/exynos/smfc/smfc.c @@ -177,6 +177,13 @@ static int smfc_vb2_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, 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 @@ -222,21 +229,62 @@ static int smfc_vb2_buf_prepare(struct vb2_buffer *vb) { 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; } @@ -413,6 +461,9 @@ static int exynos_smfc_release(struct file *filp) clk_unprepare(ctx->smfc->clk_gate2); } + kfree(ctx->quantizer_tables); + kfree(ctx->huffman_tables); + kfree(ctx); return 0; @@ -485,12 +536,26 @@ static void smfc_m2m_device_run(void *priv) 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); @@ -511,6 +576,7 @@ static void smfc_m2m_device_run(void *priv) 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); diff --git a/drivers/media/platform/exynos/smfc/smfc.h b/drivers/media/platform/exynos/smfc/smfc.h index e59a8948beb2..edf9746ffff9 100644 --- a/drivers/media/platform/exynos/smfc/smfc.h +++ b/drivers/media/platform/exynos/smfc/smfc.h @@ -76,6 +76,40 @@ struct smfc_dev { #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; @@ -87,9 +121,10 @@ struct smfc_ctx { 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; /* @@ -101,6 +136,16 @@ struct smfc_ctx { __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; @@ -125,8 +170,11 @@ static inline bool smfc_is_compressed_type(struct smfc_ctx *ctx, __u32 type) 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, -- 2.20.1