--- /dev/null
+/*
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * The H/W handling 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/kernel.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "smfc.h"
+
+void smfc_hwconfigure_reset(struct smfc_dev *smfc)
+{
+ u32 cfg = __raw_readl(smfc->reg + REG_JPEG_CNTL) & ~((1 << 29) | 3);
+ __raw_writel(cfg, smfc->reg + REG_JPEG_CNTL);
+ __raw_writel(cfg | (1 << 29), smfc->reg + REG_JPEG_CNTL);
+}
+
+/*
+ * Quantization tables in ZIG-ZAG order provided by IOC/IEC 10918-1 K.1 and K.2
+ * for YUV420 and YUV422
+ */
+static const unsigned char default_luma_qtbl[SMFC_MCU_SIZE] __aligned(64) = {
+ 16, 11, 12, 14, 12, 10, 16, 14,
+ 13, 14, 18, 17, 16, 19, 24, 40,
+ 26, 24, 22, 22, 24, 49, 35, 37,
+ 29, 40, 58, 51, 61, 60, 57, 51,
+ 56, 55, 64, 72, 92, 78, 64, 68,
+ 87, 69, 55, 56, 80, 109, 81, 87,
+ 95, 98, 103, 104, 103, 62, 77, 113,
+ 121, 112, 100, 120, 92, 101, 103, 99,
+};
+
+static const unsigned char default_chroma_qtbl[SMFC_MCU_SIZE] __aligned(64) = {
+ 17, 18, 18, 24, 21, 24, 47, 26,
+ 26, 47, 99, 66, 56, 66, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+};
+
+/* ITU Luminace Huffman Table */
+static unsigned int ITU_H_TBL_LEN_DC_LUMINANCE[] = {
+ 0x01050100, 0x01010101, 0x00000001, 0x00000000
+};
+static unsigned int ITU_H_TBL_VAL_DC_LUMINANCE[] = {
+ 0x03020100, 0x07060504, 0x0b0a0908, 0x00000000
+};
+
+/* ITU Chrominace Huffman Table */
+static unsigned int ITU_H_TBL_LEN_DC_CHROMINANCE[] = {
+ 0x01010300, 0x01010101, 0x00010101, 0x00000000
+};
+static unsigned int ITU_H_TBL_VAL_DC_CHROMINANCE[] = {
+ 0x03020100, 0x07060504, 0x0b0a0908, 0x00000000
+};
+
+/* ITU Luminace Huffman Table */
+static unsigned int ITU_H_TBL_LEN_AC_LUMINANCE[] = {
+ 0x03010200, 0x03040203, 0x04040505, 0x7d010000
+};
+
+static unsigned int ITU_H_TBL_VAL_AC_LUMINANCE[] = {
+ 0x00030201, 0x12051104, 0x06413121, 0x07615113,
+ 0x32147122, 0x08a19181, 0xc1b14223, 0xf0d15215,
+ 0x72623324, 0x160a0982, 0x1a191817, 0x28272625,
+ 0x35342a29, 0x39383736, 0x4544433a, 0x49484746,
+ 0x5554534a, 0x59585756, 0x6564635a, 0x69686766,
+ 0x7574736a, 0x79787776, 0x8584837a, 0x89888786,
+ 0x9493928a, 0x98979695, 0xa3a29a99, 0xa7a6a5a4,
+ 0xb2aaa9a8, 0xb6b5b4b3, 0xbab9b8b7, 0xc5c4c3c2,
+ 0xc9c8c7c6, 0xd4d3d2ca, 0xd8d7d6d5, 0xe2e1dad9,
+ 0xe6e5e4e3, 0xeae9e8e7, 0xf4f3f2f1, 0xf8f7f6f5,
+ 0x0000faf9
+};
+
+/* ITU Chrominace Huffman Table */
+static u32 ITU_H_TBL_LEN_AC_CHROMINANCE[] = {
+ 0x02010200, 0x04030404, 0x04040507, 0x77020100
+};
+static u32 ITU_H_TBL_VAL_AC_CHROMINANCE[] = {
+ 0x03020100, 0x21050411, 0x41120631, 0x71610751,
+ 0x81322213, 0x91421408, 0x09c1b1a1, 0xf0523323,
+ 0xd1726215, 0x3424160a, 0x17f125e1, 0x261a1918,
+ 0x2a292827, 0x38373635, 0x44433a39, 0x48474645,
+ 0x54534a49, 0x58575655, 0x64635a59, 0x68676665,
+ 0x74736a69, 0x78777675, 0x83827a79, 0x87868584,
+ 0x928a8988, 0x96959493, 0x9a999897, 0xa5a4a3a2,
+ 0xa9a8a7a6, 0xb4b3b2aa, 0xb8b7b6b5, 0xc3c2bab9,
+ 0xc7c6c5c4, 0xd2cac9c8, 0xd6d5d4d3, 0xdad9d8d7,
+ 0xe5e4e3e2, 0xe9e8e7e6, 0xf4f3f2ea, 0xf8f7f6f5,
+ 0x0000faf9
+};
+
+/* to suppress warning or error message about out-of-aaray-bound */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+/* tables and qunatization tables configuration to H/W for compression */
+static inline u32 smfc_calc_quantizers(unsigned int idx, unsigned int factor,
+ const unsigned char tbl[])
+{
+ u32 val = max(min((tbl[idx] * factor + 50) / 100, 255U), 1U);
+ val |= max(min((tbl[idx + 1] * factor + 50) / 100, 255U), 1U) << 8;
+ val |= max(min((tbl[idx + 2] * factor + 50) / 100, 255U), 1U) << 16;
+ val |= max(min((tbl[idx + 3] * factor + 50) / 100, 255U), 1U) << 24;
+ return val;
+}
+#pragma GCC diagnostic pop
+
+void smfc_hwconfigure_tables(struct smfc_ctx *ctx)
+{
+ size_t i;
+ void __iomem *base = ctx->smfc->reg;
+ unsigned int factor = ctx->quality_factor;
+ factor = (factor < 50) ? 5000 / factor : 200 - factor * 2;
+
+ /* Quantizer table 0 */
+ for (i = 0; i < SMFC_MCU_SIZE; i += 4) {
+ __raw_writel(smfc_calc_quantizers(i, factor, default_luma_qtbl),
+ base + REG_QTBL_BASE + i);
+ __raw_writel(
+ smfc_calc_quantizers(i, factor, default_chroma_qtbl),
+ base + REG_QTBL_BASE + SMFC_MCU_SIZE + i);
+ }
+
+ /* Huffman tables */
+ for (i = 0; i < 4; i++) {
+ __raw_writel(ITU_H_TBL_LEN_DC_LUMINANCE[i],
+ base + REG_HTBL_LUMA_DCLEN + i * sizeof(u32));
+ __raw_writel(ITU_H_TBL_VAL_DC_LUMINANCE[i],
+ base + REG_HTBL_LUMA_DCVAL + i * sizeof(u32));
+ __raw_writel(ITU_H_TBL_LEN_DC_CHROMINANCE[i],
+ base + REG_HTBL_CHROMA_DCLEN + i * sizeof(u32));
+ __raw_writel(ITU_H_TBL_VAL_DC_CHROMINANCE[i],
+ base + REG_HTBL_CHROMA_DCVAL + i * sizeof(u32));
+ __raw_writel(ITU_H_TBL_LEN_AC_LUMINANCE[i],
+ base + REG_HTBL_LUMA_ACLEN + i * sizeof(u32));
+ __raw_writel(ITU_H_TBL_LEN_AC_CHROMINANCE[i],
+ base + REG_HTBL_CHROMA_ACLEN + i * sizeof(u32));
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ITU_H_TBL_VAL_AC_LUMINANCE); i++)
+ __raw_writel(ITU_H_TBL_VAL_AC_LUMINANCE[i],
+ base + REG_HTBL_LUMA_ACVAL + i * sizeof(u32));
+
+ for (i = 0; i < ARRAY_SIZE(ITU_H_TBL_VAL_AC_CHROMINANCE); i++)
+ __raw_writel(ITU_H_TBL_VAL_AC_CHROMINANCE[i],
+ base + REG_HTBL_CHROMA_ACVAL + i * sizeof(u32));
+
+ __raw_writel(VAL_TABLE_SELECT, base + REG_TABLE_SELECT);
+ __raw_writel(SMFC_DHT_LEN, base + REG_DHT_LEN);
+}
+
+
+static void smfc_hwconfigure_image_base(struct smfc_ctx *ctx,
+ struct vb2_buffer *vb2buf)
+{
+ dma_addr_t addr;
+ unsigned int i;
+ bool multiplane =
+ (ctx->img_fmt->num_buffers == ctx->img_fmt->num_planes);
+
+ if (multiplane) {
+ /* Note that this includes a single-plane format such as YUYV */
+ for (i = 0; i < ctx->img_fmt->num_buffers; i++) {
+ addr = vb2_dma_sg_plane_dma_addr(vb2buf, i);
+ __raw_writel((u32)addr,
+ ctx->smfc->reg + REG_MAIN_IMAGE_BASE(i));
+ }
+ } else {
+ addr = vb2_dma_sg_plane_dma_addr(vb2buf, i);
+
+ for (i = 0; i < ctx->img_fmt->num_planes; i++) {
+ __raw_writel((u32)addr,
+ ctx->smfc->reg + REG_MAIN_IMAGE_BASE(i));
+ addr += (ctx->width * ctx->height *
+ ctx->img_fmt->bpp_pix[i]) / 8;
+ }
+ }
+}
+
+static u32 smfc_hwconfigure_jpeg_base(struct smfc_ctx *ctx,
+ struct vb2_buffer *vb2buf)
+{
+ dma_addr_t addr;
+
+ BUG_ON(vb2buf->num_planes != 1);
+ addr = vb2_dma_sg_plane_dma_addr(vb2buf, 0);
+ __raw_writel((u32)addr, ctx->smfc->reg + REG_MAIN_JPEG_BASE);
+ return (u32)addr;
+}
+
+/* [hfactor - 1][vfactor - 1]: 444, 422V, 422, 420 */
+static u32 smfc_get_jpeg_format(unsigned int hfactor, unsigned int vfactor)
+{
+ static unsigned char jpeg_regfmt[2][2] = { {1, 4}, {2, 3} };
+ return jpeg_regfmt[hfactor - 1][vfactor - 1] << 24;
+}
+
+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;
+
+ __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->m2mctx);
+ vb2buf_jpg = v4l2_m2m_next_src_buf(ctx->m2mctx);
+ } else {
+ vb2buf_img = v4l2_m2m_next_src_buf(ctx->m2mctx);
+ vb2buf_jpg = v4l2_m2m_next_dst_buf(ctx->m2mctx);
+ /*
+ * H/W JPEG does not allow upscaling of chroma components
+ * during compression
+ */
+ format |= smfc_get_jpeg_format(
+ max(ctx->chroma_hfactor, ctx->img_fmt->chroma_hfactor),
+ max(ctx->chroma_vfactor, ctx->img_fmt->chroma_vfactor));
+ }
+
+ smfc_hwconfigure_image_base(ctx, &vb2buf_img->vb2_buf);
+ stream_address = smfc_hwconfigure_jpeg_base(ctx, &vb2buf_jpg->vb2_buf);
+
+ __raw_writel(format, ctx->smfc->reg + REG_MAIN_IMAGE_FORMAT);
+ __raw_writel(vb2_plane_size(&vb2buf_jpg->vb2_buf, 0) -
+ (stream_address & SMFC_ADDR_ALIGN_MASK),
+ ctx->smfc->reg + REG_MAIN_MAX_STREAM_SIZE);
+}
+
+void smfc_hwconfigure_start(struct smfc_ctx *ctx)
+{
+ u32 cfg;
+ void __iomem *base = ctx->smfc->reg;
+
+ /* configure "Error max compressed size" interrupt */
+ cfg = __raw_readl(base + REG_INT_EN);
+ __raw_writel(cfg | (1 << 11), base + REG_INT_EN);
+
+ cfg = __raw_readl(base + REG_JPEG_CNTL) & ~3;
+ cfg |= !(ctx->flags & SMFC_CTX_COMPRESS) ? 1 : 2;
+ cfg |= 1 << 19; /* update huffman table from SFR */
+ cfg |= 1 << 28; /* enables interrupt */
+ cfg |= 1 << 29; /* Release reset */
+
+ writel(cfg, base + REG_JPEG_CNTL);
+}
+
+bool smfc_hwstatus_okay(struct smfc_dev *smfc)
+{
+ u32 val = __raw_readl(smfc->reg + REG_INT_STATUS);
+ if (!val) {
+ dev_err(smfc->dev, "Interrupt with no status change\n");
+ return false;
+ }
+
+ if ((val & ~2)) {
+ dev_err(smfc->dev, "Error interrupt %#010x\n", val);
+ return false;
+ }
+
+ return true;
+}
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/mutex.h>
+#include <linux/exynos_iovmm.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include <media/videobuf2-dma-sg.h>
#include "smfc.h"
-#include "smfc-regs.h"
/* SMFC SPECIFIC DEVICE CAPABILITIES */
/* set if H/W supports for decompression */
static irqreturn_t exynos_smfc_irq_handler(int irq, void *priv)
{
+ struct smfc_dev *smfc = priv;
+ struct smfc_ctx *ctx = v4l2_m2m_get_curr_priv(smfc->m2mdev);
+ enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
+ struct vb2_v4l2_buffer *vb_capture;
+
+ vb_capture = v4l2_m2m_dst_buf_remove(ctx->m2mctx);
+
+ if (smfc_hwstatus_okay(smfc)) {
+ vb2_set_plane_payload(&vb_capture->vb2_buf, 0,
+ smfc_get_streamsize(smfc));
+ } else {
+ state = VB2_BUF_STATE_ERROR;
+ smfc_hwconfigure_reset(smfc);
+ }
+
+ if (!IS_ERR(smfc->clk_gate)) {
+ clk_disable(smfc->clk_gate);
+ if (!IS_ERR(smfc->clk_gate2))
+ clk_disable(smfc->clk_gate2);
+ }
+
+ pm_runtime_put(smfc->dev);
+
+ v4l2_m2m_buf_done(v4l2_m2m_src_buf_remove(ctx->m2mctx), state);
+ v4l2_m2m_buf_done(vb_capture, state);
+ v4l2_m2m_job_finish(smfc->m2mdev, ctx->m2mctx);
+
return IRQ_HANDLED;
}
* default image format: YUYV
* default size: 16x8
* default chroma subsampling for JPEG: YUV422
+ * default quality factor for compression: 96
*/
ctx->img_fmt = SMFC_DEFAULT_OUTPUT_FORMAT;
ctx->width = SMFC_MIN_WIDTH << ctx->img_fmt->chroma_hfactor;
ctx->chroma_hfactor = ctx->img_fmt->chroma_hfactor;
ctx->chroma_vfactor = ctx->img_fmt->chroma_vfactor;
ctx->flags |= SMFC_CTX_COMPRESS;
+ ctx->quality_factor = 96;
ctx->smfc = smfc;
static int smfc_v4l2_reqbufs(struct file *filp, void *fh,
struct v4l2_requestbuffers *reqbufs)
{
- struct smfc_ctx *ctx = v4l2_fh_to_smfc_ctx(fh);
- return v4l2_m2m_reqbufs(filp, ctx->m2mctx, reqbufs);
+ return v4l2_m2m_reqbufs(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, reqbufs);
}
static int smfc_v4l2_querybuf(struct file *filp, void *fh,
struct v4l2_buffer *buf)
{
- struct smfc_ctx *ctx = v4l2_fh_to_smfc_ctx(fh);
- return v4l2_m2m_querybuf(filp, ctx->m2mctx, buf);
+ return v4l2_m2m_querybuf(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, buf);
}
static int smfc_v4l2_qbuf(struct file *filp, void *fh, struct v4l2_buffer *buf)
{
- struct smfc_ctx *ctx = v4l2_fh_to_smfc_ctx(fh);
- return v4l2_m2m_qbuf(filp, ctx->m2mctx, buf);
+ return v4l2_m2m_qbuf(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, buf);
}
static int smfc_v4l2_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *buf)
{
- struct smfc_ctx *ctx = v4l2_fh_to_smfc_ctx(fh);
- return v4l2_m2m_dqbuf(filp, ctx->m2mctx, buf);
+ return v4l2_m2m_dqbuf(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, buf);
+}
+
+static int smfc_v4l2_streamon(struct file *filp, void *fh,
+ enum v4l2_buf_type type)
+{
+ return v4l2_m2m_streamon(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, type);
+}
+
+static int smfc_v4l2_streamoff(struct file *filp, void *fh,
+ enum v4l2_buf_type type)
+{
+ return v4l2_m2m_streamoff(filp, v4l2_fh_to_smfc_ctx(fh)->m2mctx, type);
}
static const struct v4l2_ioctl_ops smfc_v4l2_ioctl_ops = {
.vidioc_querybuf = smfc_v4l2_querybuf,
.vidioc_qbuf = smfc_v4l2_qbuf,
.vidioc_dqbuf = smfc_v4l2_dqbuf,
+ .vidioc_streamon = smfc_v4l2_streamon,
+ .vidioc_streamoff = smfc_v4l2_streamoff,
};
static void smfc_m2m_device_run(void *priv)
{
- /* TODO: H/W initialization */
+ struct smfc_ctx *ctx = priv;
+ int ret;
+
+ ret = in_irq() ? pm_runtime_get(ctx->smfc->dev) :
+ pm_runtime_get_sync(ctx->smfc->dev);
+ if (ret < 0) {
+ pr_err("Failed to enable power\n");
+ /* TODO: error current frame */
+ }
+
+ if (!IS_ERR(ctx->smfc->clk_gate)) {
+ ret = clk_enable(ctx->smfc->clk_gate);
+ if (!ret && !IS_ERR(ctx->smfc->clk_gate2)) {
+ ret = clk_enable(ctx->smfc->clk_gate2);
+ if (ret)
+ clk_disable(ctx->smfc->clk_gate);
+ }
+ }
+
+ if (ret < 0) {
+ dev_err(ctx->smfc->dev, "Failed to enable clocks\n");
+ pm_runtime_put(ctx->smfc->dev);
+ /* TODO: error current frame */
+ }
+
+ smfc_hwconfigure_reset(ctx->smfc);
+ smfc_hwconfigure_tables(ctx);
+ smfc_hwconfigure_image(ctx);
+ smfc_hwconfigure_start(ctx);
}
static void smfc_m2m_job_abort(void *priv)
if (ret < 0)
return ret;
+ ret = iovmm_activate(&pdev->dev);
+ if (ret < 0)
+ return ret;
+
platform_set_drvdata(pdev, smfc);
dev_info(&pdev->dev, "Probed H/W Version: %02x.%02x.%04x\n",