summaryrefslogtreecommitdiffstats
path: root/drivers/media/platform/samsung
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/media/platform/samsung
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/platform/samsung')
-rw-r--r--drivers/media/platform/samsung/Kconfig10
-rw-r--r--drivers/media/platform/samsung/Makefile7
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/Kconfig10
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/Makefile4
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/gsc-core.c1324
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/gsc-core.h521
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c789
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/gsc-regs.c426
-rw-r--r--drivers/media/platform/samsung/exynos-gsc/gsc-regs.h169
-rw-r--r--drivers/media/platform/samsung/exynos4-is/Kconfig84
-rw-r--r--drivers/media/platform/samsung/exynos4-is/Makefile18
-rw-r--r--drivers/media/platform/samsung/exynos4-is/common.c47
-rw-r--r--drivers/media/platform/samsung/exynos4-is/common.h12
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-capture.c1897
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-core.c1175
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-core.h725
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-command.h134
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c269
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h245
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c157
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h12
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-param.c893
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-param.h1022
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c230
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h161
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c31
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h53
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is.c987
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-is.h356
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c659
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h41
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-isp.c789
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-isp.h197
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c346
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h155
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-lite.c1668
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-lite.h224
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-m2m.c773
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-reg.c846
-rw-r--r--drivers/media/platform/samsung/exynos4-is/fimc-reg.h338
-rw-r--r--drivers/media/platform/samsung/exynos4-is/media-dev.c1602
-rw-r--r--drivers/media/platform/samsung/exynos4-is/media-dev.h200
-rw-r--r--drivers/media/platform/samsung/exynos4-is/mipi-csis.c1035
-rw-r--r--drivers/media/platform/samsung/exynos4-is/mipi-csis.h23
-rw-r--r--drivers/media/platform/samsung/s3c-camif/Kconfig15
-rw-r--r--drivers/media/platform/samsung/s3c-camif/Makefile6
-rw-r--r--drivers/media/platform/samsung/s3c-camif/camif-capture.c1651
-rw-r--r--drivers/media/platform/samsung/s3c-camif/camif-core.c638
-rw-r--r--drivers/media/platform/samsung/s3c-camif/camif-core.h397
-rw-r--r--drivers/media/platform/samsung/s3c-camif/camif-regs.c603
-rw-r--r--drivers/media/platform/samsung/s3c-camif/camif-regs.h268
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/Kconfig11
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/Makefile4
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/g2d-hw.c113
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/g2d-regs.h118
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/g2d.c791
-rw-r--r--drivers/media/platform/samsung/s5p-g2d/g2d.h84
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/Kconfig12
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/Makefile3
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c3176
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h257
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c486
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h57
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c321
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h44
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c305
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h57
-rw-r--r--drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h646
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/Kconfig9
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/Makefile7
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h87
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h408
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h57
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h123
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/regs-mfc.h459
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c1705
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c25
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h31
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c163
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h16
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c169
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h16
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h792
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c484
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h29
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h51
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c1216
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h20
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c2703
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h20
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c88
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h23
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h28
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c124
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h339
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c1637
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h82
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c2534
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h57
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c118
-rw-r--r--drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h20
101 files changed, 44367 insertions, 0 deletions
diff --git a/drivers/media/platform/samsung/Kconfig b/drivers/media/platform/samsung/Kconfig
new file mode 100644
index 0000000000..0e34c5fc1d
--- /dev/null
+++ b/drivers/media/platform/samsung/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+comment "Samsung media platform drivers"
+
+source "drivers/media/platform/samsung/exynos-gsc/Kconfig"
+source "drivers/media/platform/samsung/exynos4-is/Kconfig"
+source "drivers/media/platform/samsung/s3c-camif/Kconfig"
+source "drivers/media/platform/samsung/s5p-g2d/Kconfig"
+source "drivers/media/platform/samsung/s5p-jpeg/Kconfig"
+source "drivers/media/platform/samsung/s5p-mfc/Kconfig"
diff --git a/drivers/media/platform/samsung/Makefile b/drivers/media/platform/samsung/Makefile
new file mode 100644
index 0000000000..21fea3330e
--- /dev/null
+++ b/drivers/media/platform/samsung/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-y += exynos-gsc/
+obj-y += exynos4-is/
+obj-y += s3c-camif/
+obj-y += s5p-g2d/
+obj-y += s5p-jpeg/
+obj-y += s5p-mfc/
diff --git a/drivers/media/platform/samsung/exynos-gsc/Kconfig b/drivers/media/platform/samsung/exynos-gsc/Kconfig
new file mode 100644
index 0000000000..7244d63c96
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_SAMSUNG_EXYNOS_GSC
+ tristate "Samsung Exynos G-Scaler driver"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on VIDEO_DEV
+ depends on ARCH_EXYNOS || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ help
+ This is a v4l2 driver for Samsung EXYNOS5 SoC G-Scaler.
diff --git a/drivers/media/platform/samsung/exynos-gsc/Makefile b/drivers/media/platform/samsung/exynos-gsc/Makefile
new file mode 100644
index 0000000000..bcefbad17a
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+exynos-gsc-objs := gsc-core.o gsc-m2m.o gsc-regs.o
+
+obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC) += exynos-gsc.o
diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-core.c b/drivers/media/platform/samsung/exynos-gsc/gsc-core.c
new file mode 100644
index 0000000000..618ae55fe3
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/gsc-core.c
@@ -0,0 +1,1324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Samsung EXYNOS5 SoC series G-Scaler driver
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <media/v4l2-ioctl.h>
+
+#include "gsc-core.h"
+
+static const struct gsc_fmt gsc_formats[] = {
+ {
+ .pixelformat = V4L2_PIX_FMT_RGB565X,
+ .depth = { 16 },
+ .color = GSC_RGB,
+ .num_planes = 1,
+ .num_comp = 1,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_BGR32,
+ .depth = { 32 },
+ .color = GSC_RGB,
+ .num_planes = 1,
+ .num_comp = 1,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 1,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_C,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 1,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_VYUY,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_C,
+ .corder = GSC_CRCB,
+ .num_planes = 1,
+ .num_comp = 1,
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YVYU,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 1,
+ .num_comp = 1,
+ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .color = GSC_YUV444,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 1,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YUV422P,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 3,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV16,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV16M,
+ .depth = { 8, 8 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 2,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV61,
+ .depth = { 16 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 1,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV61M,
+ .depth = { 8, 8 },
+ .color = GSC_YUV422,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 2,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YUV420,
+ .depth = { 12 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 3,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YVU420,
+ .depth = { 12 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 1,
+ .num_comp = 3,
+
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV12,
+ .depth = { 12 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 1,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV21,
+ .depth = { 12 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 1,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV21M,
+ .depth = { 8, 4 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 2,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV12M,
+ .depth = { 8, 4 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 2,
+ .num_comp = 2,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YUV420M,
+ .depth = { 8, 2, 2 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 3,
+ .num_comp = 3,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_YVU420M,
+ .depth = { 8, 2, 2 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CRCB,
+ .num_planes = 3,
+ .num_comp = 3,
+ }, {
+ .pixelformat = V4L2_PIX_FMT_NV12MT_16X16,
+ .depth = { 8, 4 },
+ .color = GSC_YUV420,
+ .yorder = GSC_LSB_Y,
+ .corder = GSC_CBCR,
+ .num_planes = 2,
+ .num_comp = 2,
+ }
+};
+
+const struct gsc_fmt *get_format(int index)
+{
+ if (index >= ARRAY_SIZE(gsc_formats))
+ return NULL;
+
+ return (struct gsc_fmt *)&gsc_formats[index];
+}
+
+const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index)
+{
+ const struct gsc_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+
+ if (index >= ARRAY_SIZE(gsc_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(gsc_formats); ++i) {
+ fmt = get_format(i);
+ if (pixelformat && fmt->pixelformat == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == i)
+ def_fmt = fmt;
+ }
+ return def_fmt;
+
+}
+
+void gsc_set_frame_size(struct gsc_frame *frame, int width, int height)
+{
+ frame->f_width = width;
+ frame->f_height = height;
+ frame->crop.width = width;
+ frame->crop.height = height;
+ frame->crop.left = 0;
+ frame->crop.top = 0;
+}
+
+int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst,
+ u32 *ratio)
+{
+ if ((dst > src) || (dst >= src / var->poly_sc_down_max)) {
+ *ratio = 1;
+ return 0;
+ }
+
+ if ((src / var->poly_sc_down_max / var->pre_sc_down_max) > dst) {
+ pr_err("Exceeded maximum downscaling ratio (1/16))");
+ return -EINVAL;
+ }
+
+ *ratio = (dst > (src / 8)) ? 2 : 4;
+
+ return 0;
+}
+
+void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh)
+{
+ if (hratio == 4 && vratio == 4)
+ *sh = 4;
+ else if ((hratio == 4 && vratio == 2) ||
+ (hratio == 2 && vratio == 4))
+ *sh = 3;
+ else if ((hratio == 4 && vratio == 1) ||
+ (hratio == 1 && vratio == 4) ||
+ (hratio == 2 && vratio == 2))
+ *sh = 2;
+ else if (hratio == 1 && vratio == 1)
+ *sh = 0;
+ else
+ *sh = 1;
+}
+
+void gsc_check_src_scale_info(struct gsc_variant *var,
+ struct gsc_frame *s_frame, u32 *wratio,
+ u32 tx, u32 ty, u32 *hratio)
+{
+ int remainder = 0, walign, halign;
+
+ if (is_yuv420(s_frame->fmt->color)) {
+ walign = GSC_SC_ALIGN_4;
+ halign = GSC_SC_ALIGN_4;
+ } else if (is_yuv422(s_frame->fmt->color)) {
+ walign = GSC_SC_ALIGN_4;
+ halign = GSC_SC_ALIGN_2;
+ } else {
+ walign = GSC_SC_ALIGN_2;
+ halign = GSC_SC_ALIGN_2;
+ }
+
+ remainder = s_frame->crop.width % (*wratio * walign);
+ if (remainder) {
+ s_frame->crop.width -= remainder;
+ gsc_cal_prescaler_ratio(var, s_frame->crop.width, tx, wratio);
+ pr_info("cropped src width size is recalculated from %d to %d",
+ s_frame->crop.width + remainder, s_frame->crop.width);
+ }
+
+ remainder = s_frame->crop.height % (*hratio * halign);
+ if (remainder) {
+ s_frame->crop.height -= remainder;
+ gsc_cal_prescaler_ratio(var, s_frame->crop.height, ty, hratio);
+ pr_info("cropped src height size is recalculated from %d to %d",
+ s_frame->crop.height + remainder, s_frame->crop.height);
+ }
+}
+
+int gsc_enum_fmt(struct v4l2_fmtdesc *f)
+{
+ const struct gsc_fmt *fmt;
+
+ fmt = find_fmt(NULL, NULL, f->index);
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->pixelformat;
+
+ return 0;
+}
+
+static int get_plane_info(struct gsc_frame *frm, u32 addr, u32 *index, u32 *ret_addr)
+{
+ if (frm->addr.y == addr) {
+ *index = 0;
+ *ret_addr = frm->addr.y;
+ } else if (frm->addr.cb == addr) {
+ *index = 1;
+ *ret_addr = frm->addr.cb;
+ } else if (frm->addr.cr == addr) {
+ *index = 2;
+ *ret_addr = frm->addr.cr;
+ } else {
+ pr_err("Plane address is wrong");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm)
+{
+ u32 f_chk_addr, f_chk_len, s_chk_addr = 0, s_chk_len = 0;
+
+ f_chk_addr = frm->addr.y;
+ f_chk_len = frm->payload[0];
+ if (frm->fmt->num_planes == 2) {
+ s_chk_addr = frm->addr.cb;
+ s_chk_len = frm->payload[1];
+ } else if (frm->fmt->num_planes == 3) {
+ u32 low_addr, low_plane, mid_addr, mid_plane;
+ u32 high_addr, high_plane;
+ u32 t_min, t_max;
+
+ t_min = min3(frm->addr.y, frm->addr.cb, frm->addr.cr);
+ if (get_plane_info(frm, t_min, &low_plane, &low_addr))
+ return;
+ t_max = max3(frm->addr.y, frm->addr.cb, frm->addr.cr);
+ if (get_plane_info(frm, t_max, &high_plane, &high_addr))
+ return;
+
+ mid_plane = 3 - (low_plane + high_plane);
+ if (mid_plane == 0)
+ mid_addr = frm->addr.y;
+ else if (mid_plane == 1)
+ mid_addr = frm->addr.cb;
+ else if (mid_plane == 2)
+ mid_addr = frm->addr.cr;
+ else
+ return;
+
+ f_chk_addr = low_addr;
+ if (mid_addr + frm->payload[mid_plane] - low_addr >
+ high_addr + frm->payload[high_plane] - mid_addr) {
+ f_chk_len = frm->payload[low_plane];
+ s_chk_addr = mid_addr;
+ s_chk_len = high_addr +
+ frm->payload[high_plane] - mid_addr;
+ } else {
+ f_chk_len = mid_addr +
+ frm->payload[mid_plane] - low_addr;
+ s_chk_addr = high_addr;
+ s_chk_len = frm->payload[high_plane];
+ }
+ }
+ pr_debug("f_addr = 0x%08x, f_len = %d, s_addr = 0x%08x, s_len = %d\n",
+ f_chk_addr, f_chk_len, s_chk_addr, s_chk_len);
+}
+
+int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f)
+{
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ struct gsc_variant *variant = gsc->variant;
+ struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
+ const struct gsc_fmt *fmt;
+ u32 max_w, max_h, mod_x, mod_y;
+ u32 min_w, min_h, tmp_w, tmp_h;
+ int i;
+
+ pr_debug("user put w: %d, h: %d", pix_mp->width, pix_mp->height);
+
+ fmt = find_fmt(&pix_mp->pixelformat, NULL, 0);
+ if (!fmt) {
+ pr_err("pixelformat format (0x%X) invalid\n",
+ pix_mp->pixelformat);
+ return -EINVAL;
+ }
+
+ if (pix_mp->field == V4L2_FIELD_ANY)
+ pix_mp->field = V4L2_FIELD_NONE;
+ else if (pix_mp->field != V4L2_FIELD_NONE) {
+ pr_debug("Not supported field order(%d)\n", pix_mp->field);
+ return -EINVAL;
+ }
+
+ max_w = variant->pix_max->target_rot_dis_w;
+ max_h = variant->pix_max->target_rot_dis_h;
+
+ mod_x = ffs(variant->pix_align->org_w) - 1;
+ if (is_yuv420(fmt->color))
+ mod_y = ffs(variant->pix_align->org_h) - 1;
+ else
+ mod_y = ffs(variant->pix_align->org_h) - 2;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ min_w = variant->pix_min->org_w;
+ min_h = variant->pix_min->org_h;
+ } else {
+ min_w = variant->pix_min->target_rot_dis_w;
+ min_h = variant->pix_min->target_rot_dis_h;
+ pix_mp->colorspace = ctx->out_colorspace;
+ }
+
+ pr_debug("mod_x: %d, mod_y: %d, max_w: %d, max_h = %d",
+ mod_x, mod_y, max_w, max_h);
+
+ /* To check if image size is modified to adjust parameter against
+ hardware abilities */
+ tmp_w = pix_mp->width;
+ tmp_h = pix_mp->height;
+
+ v4l_bound_align_image(&pix_mp->width, min_w, max_w, mod_x,
+ &pix_mp->height, min_h, max_h, mod_y, 0);
+ if (tmp_w != pix_mp->width || tmp_h != pix_mp->height)
+ pr_debug("Image size has been modified from %dx%d to %dx%d\n",
+ tmp_w, tmp_h, pix_mp->width, pix_mp->height);
+
+ pix_mp->num_planes = fmt->num_planes;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type))
+ ctx->out_colorspace = pix_mp->colorspace;
+
+ for (i = 0; i < pix_mp->num_planes; ++i) {
+ struct v4l2_plane_pix_format *plane_fmt = &pix_mp->plane_fmt[i];
+ u32 bpl = plane_fmt->bytesperline;
+
+ if (fmt->num_comp == 1 && /* Packed */
+ (bpl == 0 || (bpl * 8 / fmt->depth[i]) < pix_mp->width))
+ bpl = pix_mp->width * fmt->depth[i] / 8;
+
+ if (fmt->num_comp > 1 && /* Planar */
+ (bpl == 0 || bpl < pix_mp->width))
+ bpl = pix_mp->width;
+
+ if (i != 0 && fmt->num_comp == 3)
+ bpl /= 2;
+
+ plane_fmt->bytesperline = bpl;
+ plane_fmt->sizeimage = max(pix_mp->width * pix_mp->height *
+ fmt->depth[i] / 8,
+ plane_fmt->sizeimage);
+ pr_debug("[%d]: bpl: %d, sizeimage: %d",
+ i, bpl, pix_mp->plane_fmt[i].sizeimage);
+ }
+
+ return 0;
+}
+
+int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f)
+{
+ struct gsc_frame *frame;
+ struct v4l2_pix_format_mplane *pix_mp;
+ int i;
+
+ frame = ctx_get_frame(ctx, f->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ pix_mp = &f->fmt.pix_mp;
+
+ pix_mp->width = frame->f_width;
+ pix_mp->height = frame->f_height;
+ pix_mp->field = V4L2_FIELD_NONE;
+ pix_mp->pixelformat = frame->fmt->pixelformat;
+ pix_mp->num_planes = frame->fmt->num_planes;
+ pix_mp->colorspace = ctx->out_colorspace;
+
+ for (i = 0; i < pix_mp->num_planes; ++i) {
+ pix_mp->plane_fmt[i].bytesperline = (frame->f_width *
+ frame->fmt->depth[i]) / 8;
+ pix_mp->plane_fmt[i].sizeimage =
+ pix_mp->plane_fmt[i].bytesperline * frame->f_height;
+ }
+
+ return 0;
+}
+
+void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h)
+{
+ if (tmp_w != *w || tmp_h != *h) {
+ pr_info("Cropped size has been modified from %dx%d to %dx%d",
+ *w, *h, tmp_w, tmp_h);
+ *w = tmp_w;
+ *h = tmp_h;
+ }
+}
+
+int gsc_try_selection(struct gsc_ctx *ctx, struct v4l2_selection *s)
+{
+ struct gsc_frame *f;
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ struct gsc_variant *variant = gsc->variant;
+ u32 mod_x = 0, mod_y = 0, tmp_w, tmp_h;
+ u32 min_w, min_h, max_w, max_h;
+
+ if (s->r.top < 0 || s->r.left < 0) {
+ pr_err("doesn't support negative values for top & left\n");
+ return -EINVAL;
+ }
+ pr_debug("user put w: %d, h: %d", s->r.width, s->r.height);
+
+ if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ f = &ctx->d_frame;
+ else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ f = &ctx->s_frame;
+ else
+ return -EINVAL;
+
+ max_w = f->f_width;
+ max_h = f->f_height;
+ tmp_w = s->r.width;
+ tmp_h = s->r.height;
+
+ if (V4L2_TYPE_IS_OUTPUT(s->type)) {
+ if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 1) ||
+ is_rgb(f->fmt->color))
+ min_w = 32;
+ else
+ min_w = 64;
+ if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 3) ||
+ is_yuv420(f->fmt->color))
+ min_h = 32;
+ else
+ min_h = 16;
+ } else {
+ if (is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color))
+ mod_x = ffs(variant->pix_align->target_w) - 1;
+ if (is_yuv420(f->fmt->color))
+ mod_y = ffs(variant->pix_align->target_h) - 1;
+ if (ctx->gsc_ctrls.rotate->val == 90 ||
+ ctx->gsc_ctrls.rotate->val == 270) {
+ max_w = f->f_height;
+ max_h = f->f_width;
+ min_w = variant->pix_min->target_rot_en_w;
+ min_h = variant->pix_min->target_rot_en_h;
+ tmp_w = s->r.height;
+ tmp_h = s->r.width;
+ } else {
+ min_w = variant->pix_min->target_rot_dis_w;
+ min_h = variant->pix_min->target_rot_dis_h;
+ }
+ }
+ pr_debug("mod_x: %d, mod_y: %d, min_w: %d, min_h = %d",
+ mod_x, mod_y, min_w, min_h);
+ pr_debug("tmp_w : %d, tmp_h : %d", tmp_w, tmp_h);
+
+ v4l_bound_align_image(&tmp_w, min_w, max_w, mod_x,
+ &tmp_h, min_h, max_h, mod_y, 0);
+
+ if (V4L2_TYPE_IS_CAPTURE(s->type) &&
+ (ctx->gsc_ctrls.rotate->val == 90 ||
+ ctx->gsc_ctrls.rotate->val == 270))
+ gsc_check_crop_change(tmp_h, tmp_w,
+ &s->r.width, &s->r.height);
+ else
+ gsc_check_crop_change(tmp_w, tmp_h,
+ &s->r.width, &s->r.height);
+
+
+ /* adjust left/top if cropping rectangle is out of bounds */
+ /* Need to add code to algin left value with 2's multiple */
+ if (s->r.left + tmp_w > max_w)
+ s->r.left = max_w - tmp_w;
+ if (s->r.top + tmp_h > max_h)
+ s->r.top = max_h - tmp_h;
+
+ if ((is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color)) &&
+ s->r.left & 1)
+ s->r.left -= 1;
+
+ pr_debug("Aligned l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d",
+ s->r.left, s->r.top, s->r.width, s->r.height, max_w, max_h);
+
+ return 0;
+}
+
+int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw,
+ int dh, int rot, int out_path)
+{
+ int tmp_w, tmp_h, sc_down_max;
+
+ if (out_path == GSC_DMA)
+ sc_down_max = var->sc_down_max;
+ else
+ sc_down_max = var->local_sc_down;
+
+ if (rot == 90 || rot == 270) {
+ tmp_w = dh;
+ tmp_h = dw;
+ } else {
+ tmp_w = dw;
+ tmp_h = dh;
+ }
+
+ if ((sw / tmp_w) > sc_down_max ||
+ (sh / tmp_h) > sc_down_max ||
+ (tmp_w / sw) > var->sc_up_max ||
+ (tmp_h / sh) > var->sc_up_max)
+ return -EINVAL;
+
+ return 0;
+}
+
+int gsc_set_scaler_info(struct gsc_ctx *ctx)
+{
+ struct gsc_scaler *sc = &ctx->scaler;
+ struct gsc_frame *s_frame = &ctx->s_frame;
+ struct gsc_frame *d_frame = &ctx->d_frame;
+ struct gsc_variant *variant = ctx->gsc_dev->variant;
+ struct device *dev = &ctx->gsc_dev->pdev->dev;
+ int tx, ty;
+ int ret;
+
+ ret = gsc_check_scaler_ratio(variant, s_frame->crop.width,
+ s_frame->crop.height, d_frame->crop.width, d_frame->crop.height,
+ ctx->gsc_ctrls.rotate->val, ctx->out_path);
+ if (ret) {
+ pr_err("out of scaler range");
+ return ret;
+ }
+
+ if (ctx->gsc_ctrls.rotate->val == 90 ||
+ ctx->gsc_ctrls.rotate->val == 270) {
+ ty = d_frame->crop.width;
+ tx = d_frame->crop.height;
+ } else {
+ tx = d_frame->crop.width;
+ ty = d_frame->crop.height;
+ }
+
+ if (tx <= 0 || ty <= 0) {
+ dev_err(dev, "Invalid target size: %dx%d", tx, ty);
+ return -EINVAL;
+ }
+
+ ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.width,
+ tx, &sc->pre_hratio);
+ if (ret) {
+ pr_err("Horizontal scale ratio is out of range");
+ return ret;
+ }
+
+ ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.height,
+ ty, &sc->pre_vratio);
+ if (ret) {
+ pr_err("Vertical scale ratio is out of range");
+ return ret;
+ }
+
+ gsc_check_src_scale_info(variant, s_frame, &sc->pre_hratio,
+ tx, ty, &sc->pre_vratio);
+
+ gsc_get_prescaler_shfactor(sc->pre_hratio, sc->pre_vratio,
+ &sc->pre_shfactor);
+
+ sc->main_hratio = (s_frame->crop.width << 16) / tx;
+ sc->main_vratio = (s_frame->crop.height << 16) / ty;
+
+ pr_debug("scaler input/output size : sx = %d, sy = %d, tx = %d, ty = %d",
+ s_frame->crop.width, s_frame->crop.height, tx, ty);
+ pr_debug("scaler ratio info : pre_shfactor : %d, pre_h : %d",
+ sc->pre_shfactor, sc->pre_hratio);
+ pr_debug("pre_v :%d, main_h : %d, main_v : %d",
+ sc->pre_vratio, sc->main_hratio, sc->main_vratio);
+
+ return 0;
+}
+
+static int __gsc_s_ctrl(struct gsc_ctx *ctx, struct v4l2_ctrl *ctrl)
+{
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ struct gsc_variant *variant = gsc->variant;
+ unsigned int flags = GSC_DST_FMT | GSC_SRC_FMT;
+ int ret = 0;
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ ctx->hflip = ctrl->val;
+ break;
+
+ case V4L2_CID_VFLIP:
+ ctx->vflip = ctrl->val;
+ break;
+
+ case V4L2_CID_ROTATE:
+ if ((ctx->state & flags) == flags) {
+ ret = gsc_check_scaler_ratio(variant,
+ ctx->s_frame.crop.width,
+ ctx->s_frame.crop.height,
+ ctx->d_frame.crop.width,
+ ctx->d_frame.crop.height,
+ ctx->gsc_ctrls.rotate->val,
+ ctx->out_path);
+
+ if (ret)
+ return -EINVAL;
+ }
+
+ ctx->rotation = ctrl->val;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ ctx->d_frame.alpha = ctrl->val;
+ break;
+ }
+
+ ctx->state |= GSC_PARAMS;
+ return 0;
+}
+
+static int gsc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct gsc_ctx *ctx = ctrl_to_ctx(ctrl);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctx->gsc_dev->slock, flags);
+ ret = __gsc_s_ctrl(ctx, ctrl);
+ spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops gsc_ctrl_ops = {
+ .s_ctrl = gsc_s_ctrl,
+};
+
+int gsc_ctrls_create(struct gsc_ctx *ctx)
+{
+ if (ctx->ctrls_rdy) {
+ pr_err("Control handler of this context was created already");
+ return 0;
+ }
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, GSC_MAX_CTRL_NUM);
+
+ ctx->gsc_ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler,
+ &gsc_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0);
+ ctx->gsc_ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler,
+ &gsc_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctx->gsc_ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler,
+ &gsc_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+ ctx->gsc_ctrls.global_alpha = v4l2_ctrl_new_std(&ctx->ctrl_handler,
+ &gsc_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);
+
+ ctx->ctrls_rdy = ctx->ctrl_handler.error == 0;
+
+ if (ctx->ctrl_handler.error) {
+ int err = ctx->ctrl_handler.error;
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ pr_err("Failed to create G-Scaler control handlers");
+ return err;
+ }
+
+ return 0;
+}
+
+void gsc_ctrls_delete(struct gsc_ctx *ctx)
+{
+ if (ctx->ctrls_rdy) {
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ ctx->ctrls_rdy = false;
+ }
+}
+
+/* The color format (num_comp, num_planes) must be already configured. */
+int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb,
+ struct gsc_frame *frame, struct gsc_addr *addr)
+{
+ int ret = 0;
+ u32 pix_size;
+
+ if ((vb == NULL) || (frame == NULL))
+ return -EINVAL;
+
+ pix_size = frame->f_width * frame->f_height;
+
+ pr_debug("num_planes= %d, num_comp= %d, pix_size= %d",
+ frame->fmt->num_planes, frame->fmt->num_comp, pix_size);
+
+ addr->y = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ if (frame->fmt->num_planes == 1) {
+ switch (frame->fmt->num_comp) {
+ case 1:
+ addr->cb = 0;
+ addr->cr = 0;
+ break;
+ case 2:
+ /* decompose Y into Y/Cb */
+ addr->cb = (dma_addr_t)(addr->y + pix_size);
+ addr->cr = 0;
+ break;
+ case 3:
+ /* decompose Y into Y/Cb/Cr */
+ addr->cb = (dma_addr_t)(addr->y + pix_size);
+ if (GSC_YUV420 == frame->fmt->color)
+ addr->cr = (dma_addr_t)(addr->cb
+ + (pix_size >> 2));
+ else /* 422 */
+ addr->cr = (dma_addr_t)(addr->cb
+ + (pix_size >> 1));
+ break;
+ default:
+ pr_err("Invalid the number of color planes");
+ return -EINVAL;
+ }
+ } else {
+ if (frame->fmt->num_planes >= 2)
+ addr->cb = vb2_dma_contig_plane_dma_addr(vb, 1);
+
+ if (frame->fmt->num_planes == 3)
+ addr->cr = vb2_dma_contig_plane_dma_addr(vb, 2);
+ }
+
+ if ((frame->fmt->pixelformat == V4L2_PIX_FMT_VYUY) ||
+ (frame->fmt->pixelformat == V4L2_PIX_FMT_YVYU) ||
+ (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420) ||
+ (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420M))
+ swap(addr->cb, addr->cr);
+
+ pr_debug("ADDR: y= %pad cb= %pad cr= %pad ret= %d",
+ &addr->y, &addr->cb, &addr->cr, ret);
+
+ return ret;
+}
+
+static irqreturn_t gsc_irq_handler(int irq, void *priv)
+{
+ struct gsc_dev *gsc = priv;
+ struct gsc_ctx *ctx;
+ int gsc_irq;
+
+ gsc_irq = gsc_hw_get_irq_status(gsc);
+ gsc_hw_clear_irq(gsc, gsc_irq);
+
+ if (gsc_irq == GSC_IRQ_OVERRUN) {
+ pr_err("Local path input over-run interrupt has occurred!\n");
+ return IRQ_HANDLED;
+ }
+
+ spin_lock(&gsc->slock);
+
+ if (test_and_clear_bit(ST_M2M_PEND, &gsc->state)) {
+
+ gsc_hw_enable_control(gsc, false);
+
+ if (test_and_clear_bit(ST_M2M_SUSPENDING, &gsc->state)) {
+ set_bit(ST_M2M_SUSPENDED, &gsc->state);
+ wake_up(&gsc->irq_queue);
+ goto isr_unlock;
+ }
+ ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev);
+
+ if (!ctx || !ctx->m2m_ctx)
+ goto isr_unlock;
+
+ spin_unlock(&gsc->slock);
+ gsc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE);
+
+ /* wake_up job_abort, stop_streaming */
+ if (ctx->state & GSC_CTX_STOP_REQ) {
+ ctx->state &= ~GSC_CTX_STOP_REQ;
+ wake_up(&gsc->irq_queue);
+ }
+ return IRQ_HANDLED;
+ }
+
+isr_unlock:
+ spin_unlock(&gsc->slock);
+ return IRQ_HANDLED;
+}
+
+static struct gsc_pix_max gsc_v_100_max = {
+ .org_scaler_bypass_w = 8192,
+ .org_scaler_bypass_h = 8192,
+ .org_scaler_input_w = 4800,
+ .org_scaler_input_h = 3344,
+ .real_rot_dis_w = 4800,
+ .real_rot_dis_h = 3344,
+ .real_rot_en_w = 2047,
+ .real_rot_en_h = 2047,
+ .target_rot_dis_w = 4800,
+ .target_rot_dis_h = 3344,
+ .target_rot_en_w = 2016,
+ .target_rot_en_h = 2016,
+};
+
+static struct gsc_pix_max gsc_v_5250_max = {
+ .org_scaler_bypass_w = 8192,
+ .org_scaler_bypass_h = 8192,
+ .org_scaler_input_w = 4800,
+ .org_scaler_input_h = 3344,
+ .real_rot_dis_w = 4800,
+ .real_rot_dis_h = 3344,
+ .real_rot_en_w = 2016,
+ .real_rot_en_h = 2016,
+ .target_rot_dis_w = 4800,
+ .target_rot_dis_h = 3344,
+ .target_rot_en_w = 2016,
+ .target_rot_en_h = 2016,
+};
+
+static struct gsc_pix_max gsc_v_5420_max = {
+ .org_scaler_bypass_w = 8192,
+ .org_scaler_bypass_h = 8192,
+ .org_scaler_input_w = 4800,
+ .org_scaler_input_h = 3344,
+ .real_rot_dis_w = 4800,
+ .real_rot_dis_h = 3344,
+ .real_rot_en_w = 2048,
+ .real_rot_en_h = 2048,
+ .target_rot_dis_w = 4800,
+ .target_rot_dis_h = 3344,
+ .target_rot_en_w = 2016,
+ .target_rot_en_h = 2016,
+};
+
+static struct gsc_pix_max gsc_v_5433_max = {
+ .org_scaler_bypass_w = 8192,
+ .org_scaler_bypass_h = 8192,
+ .org_scaler_input_w = 4800,
+ .org_scaler_input_h = 3344,
+ .real_rot_dis_w = 4800,
+ .real_rot_dis_h = 3344,
+ .real_rot_en_w = 2047,
+ .real_rot_en_h = 2047,
+ .target_rot_dis_w = 4800,
+ .target_rot_dis_h = 3344,
+ .target_rot_en_w = 2016,
+ .target_rot_en_h = 2016,
+};
+
+static struct gsc_pix_min gsc_v_100_min = {
+ .org_w = 64,
+ .org_h = 32,
+ .real_w = 64,
+ .real_h = 32,
+ .target_rot_dis_w = 64,
+ .target_rot_dis_h = 32,
+ .target_rot_en_w = 32,
+ .target_rot_en_h = 16,
+};
+
+static struct gsc_pix_align gsc_v_100_align = {
+ .org_h = 16,
+ .org_w = 16, /* yuv420 : 16, others : 8 */
+ .offset_h = 2, /* yuv420/422 : 2, others : 1 */
+ .real_w = 16, /* yuv420/422 : 4~16, others : 2~8 */
+ .real_h = 16, /* yuv420 : 4~16, others : 1 */
+ .target_w = 2, /* yuv420/422 : 2, others : 1 */
+ .target_h = 2, /* yuv420 : 2, others : 1 */
+};
+
+static struct gsc_variant gsc_v_100_variant = {
+ .pix_max = &gsc_v_100_max,
+ .pix_min = &gsc_v_100_min,
+ .pix_align = &gsc_v_100_align,
+ .in_buf_cnt = 32,
+ .out_buf_cnt = 32,
+ .sc_up_max = 8,
+ .sc_down_max = 16,
+ .poly_sc_down_max = 4,
+ .pre_sc_down_max = 4,
+ .local_sc_down = 2,
+};
+
+static struct gsc_variant gsc_v_5250_variant = {
+ .pix_max = &gsc_v_5250_max,
+ .pix_min = &gsc_v_100_min,
+ .pix_align = &gsc_v_100_align,
+ .in_buf_cnt = 32,
+ .out_buf_cnt = 32,
+ .sc_up_max = 8,
+ .sc_down_max = 16,
+ .poly_sc_down_max = 4,
+ .pre_sc_down_max = 4,
+ .local_sc_down = 2,
+};
+
+static struct gsc_variant gsc_v_5420_variant = {
+ .pix_max = &gsc_v_5420_max,
+ .pix_min = &gsc_v_100_min,
+ .pix_align = &gsc_v_100_align,
+ .in_buf_cnt = 32,
+ .out_buf_cnt = 32,
+ .sc_up_max = 8,
+ .sc_down_max = 16,
+ .poly_sc_down_max = 4,
+ .pre_sc_down_max = 4,
+ .local_sc_down = 2,
+};
+
+static struct gsc_variant gsc_v_5433_variant = {
+ .pix_max = &gsc_v_5433_max,
+ .pix_min = &gsc_v_100_min,
+ .pix_align = &gsc_v_100_align,
+ .in_buf_cnt = 32,
+ .out_buf_cnt = 32,
+ .sc_up_max = 8,
+ .sc_down_max = 16,
+ .poly_sc_down_max = 4,
+ .pre_sc_down_max = 4,
+ .local_sc_down = 2,
+};
+
+static struct gsc_driverdata gsc_v_100_drvdata = {
+ .variant = {
+ [0] = &gsc_v_100_variant,
+ [1] = &gsc_v_100_variant,
+ [2] = &gsc_v_100_variant,
+ [3] = &gsc_v_100_variant,
+ },
+ .num_entities = 4,
+ .clk_names = { "gscl" },
+ .num_clocks = 1,
+};
+
+static struct gsc_driverdata gsc_v_5250_drvdata = {
+ .variant = {
+ [0] = &gsc_v_5250_variant,
+ [1] = &gsc_v_5250_variant,
+ [2] = &gsc_v_5250_variant,
+ [3] = &gsc_v_5250_variant,
+ },
+ .num_entities = 4,
+ .clk_names = { "gscl" },
+ .num_clocks = 1,
+};
+
+static struct gsc_driverdata gsc_v_5420_drvdata = {
+ .variant = {
+ [0] = &gsc_v_5420_variant,
+ [1] = &gsc_v_5420_variant,
+ },
+ .num_entities = 2,
+ .clk_names = { "gscl" },
+ .num_clocks = 1,
+};
+
+static struct gsc_driverdata gsc_5433_drvdata = {
+ .variant = {
+ [0] = &gsc_v_5433_variant,
+ [1] = &gsc_v_5433_variant,
+ [2] = &gsc_v_5433_variant,
+ },
+ .num_entities = 3,
+ .clk_names = { "pclk", "aclk", "aclk_xiu", "aclk_gsclbend" },
+ .num_clocks = 4,
+};
+
+static const struct of_device_id exynos_gsc_match[] = {
+ {
+ .compatible = "samsung,exynos5250-gsc",
+ .data = &gsc_v_5250_drvdata,
+ },
+ {
+ .compatible = "samsung,exynos5420-gsc",
+ .data = &gsc_v_5420_drvdata,
+ },
+ {
+ .compatible = "samsung,exynos5433-gsc",
+ .data = &gsc_5433_drvdata,
+ },
+ {
+ .compatible = "samsung,exynos5-gsc",
+ .data = &gsc_v_100_drvdata,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_gsc_match);
+
+static int gsc_probe(struct platform_device *pdev)
+{
+ struct gsc_dev *gsc;
+ struct device *dev = &pdev->dev;
+ const struct gsc_driverdata *drv_data = of_device_get_match_data(dev);
+ int irq;
+ int ret;
+ int i;
+
+ gsc = devm_kzalloc(dev, sizeof(struct gsc_dev), GFP_KERNEL);
+ if (!gsc)
+ return -ENOMEM;
+
+ ret = of_alias_get_id(pdev->dev.of_node, "gsc");
+ if (ret < 0)
+ return ret;
+
+ if (drv_data == &gsc_v_100_drvdata)
+ dev_info(dev, "compatible 'exynos5-gsc' is deprecated\n");
+
+ gsc->id = ret;
+ if (gsc->id >= drv_data->num_entities) {
+ dev_err(dev, "Invalid platform device id: %d\n", gsc->id);
+ return -EINVAL;
+ }
+
+ gsc->num_clocks = drv_data->num_clocks;
+ gsc->variant = drv_data->variant[gsc->id];
+ gsc->pdev = pdev;
+
+ init_waitqueue_head(&gsc->irq_queue);
+ spin_lock_init(&gsc->slock);
+ mutex_init(&gsc->lock);
+
+ gsc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gsc->regs))
+ return PTR_ERR(gsc->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ for (i = 0; i < gsc->num_clocks; i++) {
+ gsc->clock[i] = devm_clk_get(dev, drv_data->clk_names[i]);
+ if (IS_ERR(gsc->clock[i])) {
+ dev_err(dev, "failed to get clock: %s\n",
+ drv_data->clk_names[i]);
+ return PTR_ERR(gsc->clock[i]);
+ }
+ }
+
+ for (i = 0; i < gsc->num_clocks; i++) {
+ ret = clk_prepare_enable(gsc->clock[i]);
+ if (ret) {
+ dev_err(dev, "clock prepare failed for clock: %s\n",
+ drv_data->clk_names[i]);
+ while (--i >= 0)
+ clk_disable_unprepare(gsc->clock[i]);
+ return ret;
+ }
+ }
+
+ ret = devm_request_irq(dev, irq, gsc_irq_handler,
+ 0, pdev->name, gsc);
+ if (ret) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ goto err_clk;
+ }
+
+ ret = v4l2_device_register(dev, &gsc->v4l2_dev);
+ if (ret)
+ goto err_clk;
+
+ ret = gsc_register_m2m_device(gsc);
+ if (ret)
+ goto err_v4l2;
+
+ platform_set_drvdata(pdev, gsc);
+
+ gsc_hw_set_sw_reset(gsc);
+ gsc_wait_reset(gsc);
+
+ vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ dev_dbg(dev, "gsc-%d registered successfully\n", gsc->id);
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+
+ return 0;
+
+err_v4l2:
+ v4l2_device_unregister(&gsc->v4l2_dev);
+err_clk:
+ for (i = gsc->num_clocks - 1; i >= 0; i--)
+ clk_disable_unprepare(gsc->clock[i]);
+ return ret;
+}
+
+static void gsc_remove(struct platform_device *pdev)
+{
+ struct gsc_dev *gsc = platform_get_drvdata(pdev);
+ int i;
+
+ gsc_unregister_m2m_device(gsc);
+ v4l2_device_unregister(&gsc->v4l2_dev);
+
+ vb2_dma_contig_clear_max_seg_size(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ for (i = 0; i < gsc->num_clocks; i++)
+ clk_disable_unprepare(gsc->clock[i]);
+
+ pm_runtime_set_suspended(&pdev->dev);
+
+ dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name);
+}
+
+#ifdef CONFIG_PM
+static int gsc_m2m_suspend(struct gsc_dev *gsc)
+{
+ unsigned long flags;
+ int timeout;
+
+ spin_lock_irqsave(&gsc->slock, flags);
+ if (!gsc_m2m_pending(gsc)) {
+ spin_unlock_irqrestore(&gsc->slock, flags);
+ return 0;
+ }
+ clear_bit(ST_M2M_SUSPENDED, &gsc->state);
+ set_bit(ST_M2M_SUSPENDING, &gsc->state);
+ spin_unlock_irqrestore(&gsc->slock, flags);
+
+ timeout = wait_event_timeout(gsc->irq_queue,
+ test_bit(ST_M2M_SUSPENDED, &gsc->state),
+ GSC_SHUTDOWN_TIMEOUT);
+
+ clear_bit(ST_M2M_SUSPENDING, &gsc->state);
+ return timeout == 0 ? -EAGAIN : 0;
+}
+
+static void gsc_m2m_resume(struct gsc_dev *gsc)
+{
+ struct gsc_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&gsc->slock, flags);
+ /* Clear for full H/W setup in first run after resume */
+ ctx = gsc->m2m.ctx;
+ gsc->m2m.ctx = NULL;
+ spin_unlock_irqrestore(&gsc->slock, flags);
+
+ if (test_and_clear_bit(ST_M2M_SUSPENDED, &gsc->state))
+ gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
+}
+
+static int gsc_runtime_resume(struct device *dev)
+{
+ struct gsc_dev *gsc = dev_get_drvdata(dev);
+ int ret = 0;
+ int i;
+
+ pr_debug("gsc%d: state: 0x%lx\n", gsc->id, gsc->state);
+
+ for (i = 0; i < gsc->num_clocks; i++) {
+ ret = clk_prepare_enable(gsc->clock[i]);
+ if (ret) {
+ while (--i >= 0)
+ clk_disable_unprepare(gsc->clock[i]);
+ return ret;
+ }
+ }
+
+ gsc_hw_set_sw_reset(gsc);
+ gsc_wait_reset(gsc);
+ gsc_m2m_resume(gsc);
+
+ return 0;
+}
+
+static int gsc_runtime_suspend(struct device *dev)
+{
+ struct gsc_dev *gsc = dev_get_drvdata(dev);
+ int ret = 0;
+ int i;
+
+ ret = gsc_m2m_suspend(gsc);
+ if (ret)
+ return ret;
+
+ for (i = gsc->num_clocks - 1; i >= 0; i--)
+ clk_disable_unprepare(gsc->clock[i]);
+
+ pr_debug("gsc%d: state: 0x%lx\n", gsc->id, gsc->state);
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops gsc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(gsc_runtime_suspend, gsc_runtime_resume, NULL)
+};
+
+static struct platform_driver gsc_driver = {
+ .probe = gsc_probe,
+ .remove_new = gsc_remove,
+ .driver = {
+ .name = GSC_MODULE_NAME,
+ .pm = &gsc_pm_ops,
+ .of_match_table = exynos_gsc_match,
+ }
+};
+
+module_platform_driver(gsc_driver);
+
+MODULE_AUTHOR("Hyunwong Kim <khw0178.kim@samsung.com>");
+MODULE_DESCRIPTION("Samsung EXYNOS5 Soc series G-Scaler driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-core.h b/drivers/media/platform/samsung/exynos-gsc/gsc-core.h
new file mode 100644
index 0000000000..1ea5fa1bf3
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/gsc-core.h
@@ -0,0 +1,521 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * header file for Samsung EXYNOS5 SoC series G-Scaler driver
+
+ */
+
+#ifndef GSC_CORE_H_
+#define GSC_CORE_H_
+
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-mediabus.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "gsc-regs.h"
+
+#define CONFIG_VB2_GSC_DMA_CONTIG 1
+#define GSC_MODULE_NAME "exynos-gsc"
+
+#define GSC_SHUTDOWN_TIMEOUT ((100*HZ)/1000)
+#define GSC_MAX_DEVS 4
+#define GSC_MAX_CLOCKS 4
+#define GSC_M2M_BUF_NUM 0
+#define GSC_MAX_CTRL_NUM 10
+#define GSC_SC_ALIGN_4 4
+#define GSC_SC_ALIGN_2 2
+#define DEFAULT_CSC_EQ 1
+#define DEFAULT_CSC_RANGE 1
+
+#define GSC_PARAMS (1 << 0)
+#define GSC_SRC_FMT (1 << 1)
+#define GSC_DST_FMT (1 << 2)
+#define GSC_CTX_M2M (1 << 3)
+#define GSC_CTX_STOP_REQ (1 << 6)
+#define GSC_CTX_ABORT (1 << 7)
+
+enum gsc_dev_flags {
+ /* for m2m node */
+ ST_M2M_OPEN,
+ ST_M2M_RUN,
+ ST_M2M_PEND,
+ ST_M2M_SUSPENDED,
+ ST_M2M_SUSPENDING,
+};
+
+enum gsc_irq {
+ GSC_IRQ_DONE,
+ GSC_IRQ_OVERRUN
+};
+
+/**
+ * enum gsc_datapath - the path of data used for G-Scaler
+ * @GSC_CAMERA: from camera
+ * @GSC_DMA: from/to DMA
+ * @GSC_WRITEBACK: from FIMD
+ */
+enum gsc_datapath {
+ GSC_CAMERA = 0x1,
+ GSC_DMA,
+ GSC_WRITEBACK,
+};
+
+enum gsc_color_fmt {
+ GSC_RGB = 0x1,
+ GSC_YUV420 = 0x2,
+ GSC_YUV422 = 0x4,
+ GSC_YUV444 = 0x8,
+};
+
+enum gsc_yuv_fmt {
+ GSC_LSB_Y = 0x10,
+ GSC_LSB_C,
+ GSC_CBCR = 0x20,
+ GSC_CRCB,
+};
+
+#define fh_to_ctx(__fh) container_of(__fh, struct gsc_ctx, fh)
+#define is_rgb(x) (!!((x) & 0x1))
+#define is_yuv420(x) (!!((x) & 0x2))
+#define is_yuv422(x) (!!((x) & 0x4))
+
+#define gsc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state)
+#define gsc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state)
+#define gsc_m2m_opened(dev) test_bit(ST_M2M_OPEN, &(dev)->state)
+
+#define ctrl_to_ctx(__ctrl) \
+ container_of((__ctrl)->handler, struct gsc_ctx, ctrl_handler)
+/**
+ * struct gsc_fmt - the driver's internal color format data
+ * @mbus_code: Media Bus pixel code, -1 if not applicable
+ * @pixelformat: the fourcc code for this format, 0 if not applicable
+ * @color: color encoding
+ * @yorder: Y/C order
+ * @corder: Chrominance order control
+ * @num_planes: number of physically non-contiguous data planes
+ * @num_comp: number of physically contiguous data planes
+ * @depth: per plane driver's private 'number of bits per pixel'
+ * @flags: flags indicating which operation mode format applies to
+ */
+struct gsc_fmt {
+ u32 mbus_code;
+ u32 pixelformat;
+ u32 color;
+ u32 yorder;
+ u32 corder;
+ u16 num_planes;
+ u16 num_comp;
+ u8 depth[VIDEO_MAX_PLANES];
+ u32 flags;
+};
+
+/**
+ * struct gsc_input_buf - the driver's video buffer
+ * @vb: videobuf2 buffer
+ * @list : linked list structure for buffer queue
+ * @idx : index of G-Scaler input buffer
+ */
+struct gsc_input_buf {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ int idx;
+};
+
+/**
+ * struct gsc_addr - the G-Scaler physical address set
+ * @y: luminance plane address
+ * @cb: Cb plane address
+ * @cr: Cr plane address
+ */
+struct gsc_addr {
+ dma_addr_t y;
+ dma_addr_t cb;
+ dma_addr_t cr;
+};
+
+/* struct gsc_ctrls - the G-Scaler control set
+ * @rotate: rotation degree
+ * @hflip: horizontal flip
+ * @vflip: vertical flip
+ * @global_alpha: the alpha value of current frame
+ */
+struct gsc_ctrls {
+ struct v4l2_ctrl *rotate;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *global_alpha;
+};
+
+/**
+ * struct gsc_scaler - the configuration data for G-Scaler inetrnal scaler
+ * @pre_shfactor: pre sclaer shift factor
+ * @pre_hratio: horizontal ratio of the prescaler
+ * @pre_vratio: vertical ratio of the prescaler
+ * @main_hratio: the main scaler's horizontal ratio
+ * @main_vratio: the main scaler's vertical ratio
+ */
+struct gsc_scaler {
+ u32 pre_shfactor;
+ u32 pre_hratio;
+ u32 pre_vratio;
+ u32 main_hratio;
+ u32 main_vratio;
+};
+
+struct gsc_dev;
+
+struct gsc_ctx;
+
+/**
+ * struct gsc_frame - source/target frame properties
+ * @f_width: SRC : SRCIMG_WIDTH, DST : OUTPUTDMA_WHOLE_IMG_WIDTH
+ * @f_height: SRC : SRCIMG_HEIGHT, DST : OUTPUTDMA_WHOLE_IMG_HEIGHT
+ * @crop: cropped(source)/scaled(destination) size
+ * @payload: image size in bytes (w x h x bpp)
+ * @addr: image frame buffer physical addresses
+ * @fmt: G-Scaler color format pointer
+ * @colorspace: value indicating v4l2_colorspace
+ * @alpha: frame's alpha value
+ */
+struct gsc_frame {
+ u32 f_width;
+ u32 f_height;
+ struct v4l2_rect crop;
+ unsigned long payload[VIDEO_MAX_PLANES];
+ struct gsc_addr addr;
+ const struct gsc_fmt *fmt;
+ u32 colorspace;
+ u8 alpha;
+};
+
+/**
+ * struct gsc_m2m_device - v4l2 memory-to-memory device data
+ * @vfd: the video device node for v4l2 m2m mode
+ * @m2m_dev: v4l2 memory-to-memory device data
+ * @ctx: hardware context data
+ * @refcnt: the reference counter
+ */
+struct gsc_m2m_device {
+ struct video_device *vfd;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct gsc_ctx *ctx;
+ int refcnt;
+};
+
+/**
+ * struct gsc_pix_max - image pixel size limits in various IP configurations
+ *
+ * @org_scaler_bypass_w: max pixel width when the scaler is disabled
+ * @org_scaler_bypass_h: max pixel height when the scaler is disabled
+ * @org_scaler_input_w: max pixel width when the scaler is enabled
+ * @org_scaler_input_h: max pixel height when the scaler is enabled
+ * @real_rot_dis_w: max pixel src cropped height with the rotator is off
+ * @real_rot_dis_h: max pixel src cropped width with the rotator is off
+ * @real_rot_en_w: max pixel src cropped width with the rotator is on
+ * @real_rot_en_h: max pixel src cropped height with the rotator is on
+ * @target_rot_dis_w: max pixel dst scaled width with the rotator is off
+ * @target_rot_dis_h: max pixel dst scaled height with the rotator is off
+ * @target_rot_en_w: max pixel dst scaled width with the rotator is on
+ * @target_rot_en_h: max pixel dst scaled height with the rotator is on
+ */
+struct gsc_pix_max {
+ u16 org_scaler_bypass_w;
+ u16 org_scaler_bypass_h;
+ u16 org_scaler_input_w;
+ u16 org_scaler_input_h;
+ u16 real_rot_dis_w;
+ u16 real_rot_dis_h;
+ u16 real_rot_en_w;
+ u16 real_rot_en_h;
+ u16 target_rot_dis_w;
+ u16 target_rot_dis_h;
+ u16 target_rot_en_w;
+ u16 target_rot_en_h;
+};
+
+/**
+ * struct gsc_pix_min - image pixel size limits in various IP configurations
+ *
+ * @org_w: minimum source pixel width
+ * @org_h: minimum source pixel height
+ * @real_w: minimum input crop pixel width
+ * @real_h: minimum input crop pixel height
+ * @target_rot_dis_w: minimum output scaled pixel height when rotator is off
+ * @target_rot_dis_h: minimum output scaled pixel height when rotator is off
+ * @target_rot_en_w: minimum output scaled pixel height when rotator is on
+ * @target_rot_en_h: minimum output scaled pixel height when rotator is on
+ */
+struct gsc_pix_min {
+ u16 org_w;
+ u16 org_h;
+ u16 real_w;
+ u16 real_h;
+ u16 target_rot_dis_w;
+ u16 target_rot_dis_h;
+ u16 target_rot_en_w;
+ u16 target_rot_en_h;
+};
+
+struct gsc_pix_align {
+ u16 org_h;
+ u16 org_w;
+ u16 offset_h;
+ u16 real_w;
+ u16 real_h;
+ u16 target_w;
+ u16 target_h;
+};
+
+/*
+ * struct gsc_variant - G-Scaler variant information
+ */
+struct gsc_variant {
+ struct gsc_pix_max *pix_max;
+ struct gsc_pix_min *pix_min;
+ struct gsc_pix_align *pix_align;
+ u16 in_buf_cnt;
+ u16 out_buf_cnt;
+ u16 sc_up_max;
+ u16 sc_down_max;
+ u16 poly_sc_down_max;
+ u16 pre_sc_down_max;
+ u16 local_sc_down;
+};
+
+/**
+ * struct gsc_driverdata - per device type driver data for init time.
+ *
+ * @variant: the variant information for this driver.
+ * @num_entities: the number of g-scalers
+ * @clk_names: clock names
+ * @num_clocks: the number of clocks in @clk_names
+ * @num_entities: the number of g-scalers
+ */
+struct gsc_driverdata {
+ struct gsc_variant *variant[GSC_MAX_DEVS];
+ const char *clk_names[GSC_MAX_CLOCKS];
+ int num_clocks;
+ int num_entities;
+};
+
+/**
+ * struct gsc_dev - abstraction for G-Scaler entity
+ * @slock: the spinlock protecting this data structure
+ * @lock: the mutex protecting this data structure
+ * @pdev: pointer to the G-Scaler platform device
+ * @variant: the IP variant information
+ * @id: G-Scaler device index (0..GSC_MAX_DEVS)
+ * @num_clocks: number of clocks required for G-Scaler operation
+ * @clock: clocks required for G-Scaler operation
+ * @regs: the mapped hardware registers
+ * @irq_queue: interrupt handler waitqueue
+ * @m2m: memory-to-memory V4L2 device information
+ * @state: flags used to synchronize m2m and capture mode operation
+ * @vdev: video device for G-Scaler instance
+ * @v4l2_dev: v4l2_device for G-Scaler instance
+ */
+struct gsc_dev {
+ spinlock_t slock;
+ struct mutex lock;
+ struct platform_device *pdev;
+ struct gsc_variant *variant;
+ u16 id;
+ int num_clocks;
+ struct clk *clock[GSC_MAX_CLOCKS];
+ void __iomem *regs;
+ wait_queue_head_t irq_queue;
+ struct gsc_m2m_device m2m;
+ unsigned long state;
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+};
+
+/**
+ * struct gsc_ctx - the device context data
+ * @s_frame: source frame properties
+ * @d_frame: destination frame properties
+ * @in_path: input mode (DMA or camera)
+ * @out_path: output mode (DMA or FIFO)
+ * @scaler: image scaler properties
+ * @flags: additional flags for image conversion
+ * @state: flags to keep track of user configuration
+ * @rotation: rotation
+ * @hflip: horizontal flip
+ * @vflip: vertical flip
+ * @gsc_dev: the G-Scaler device this context applies to
+ * @m2m_ctx: memory-to-memory device context
+ * @fh: v4l2 file handle
+ * @ctrl_handler: v4l2 controls handler
+ * @gsc_ctrls: G-Scaler control set
+ * @ctrls_rdy: true if the control handler is initialized
+ * @out_colorspace: the colorspace of the OUTPUT queue
+ */
+struct gsc_ctx {
+ struct gsc_frame s_frame;
+ struct gsc_frame d_frame;
+ enum gsc_datapath in_path;
+ enum gsc_datapath out_path;
+ struct gsc_scaler scaler;
+ u32 flags;
+ u32 state;
+ int rotation;
+ unsigned int hflip:1;
+ unsigned int vflip:1;
+ struct gsc_dev *gsc_dev;
+ struct v4l2_m2m_ctx *m2m_ctx;
+ struct v4l2_fh fh;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct gsc_ctrls gsc_ctrls;
+ bool ctrls_rdy;
+ enum v4l2_colorspace out_colorspace;
+};
+
+void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm);
+int gsc_register_m2m_device(struct gsc_dev *gsc);
+void gsc_unregister_m2m_device(struct gsc_dev *gsc);
+void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state);
+
+u32 get_plane_size(struct gsc_frame *fr, unsigned int plane);
+const struct gsc_fmt *get_format(int index);
+const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index);
+int gsc_enum_fmt(struct v4l2_fmtdesc *f);
+int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f);
+void gsc_set_frame_size(struct gsc_frame *frame, int width, int height);
+int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f);
+void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h);
+int gsc_try_selection(struct gsc_ctx *ctx, struct v4l2_selection *s);
+int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst,
+ u32 *ratio);
+void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh);
+void gsc_check_src_scale_info(struct gsc_variant *var,
+ struct gsc_frame *s_frame,
+ u32 *wratio, u32 tx, u32 ty, u32 *hratio);
+int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw,
+ int dh, int rot, int out_path);
+int gsc_set_scaler_info(struct gsc_ctx *ctx);
+int gsc_ctrls_create(struct gsc_ctx *ctx);
+void gsc_ctrls_delete(struct gsc_ctx *ctx);
+int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb,
+ struct gsc_frame *frame, struct gsc_addr *addr);
+
+static inline void gsc_ctx_state_lock_set(u32 state, struct gsc_ctx *ctx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->gsc_dev->slock, flags);
+ ctx->state |= state;
+ spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags);
+}
+
+static inline void gsc_ctx_state_lock_clear(u32 state, struct gsc_ctx *ctx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->gsc_dev->slock, flags);
+ ctx->state &= ~state;
+ spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags);
+}
+
+static inline int is_tiled(const struct gsc_fmt *fmt)
+{
+ return fmt->pixelformat == V4L2_PIX_FMT_NV12MT_16X16;
+}
+
+static inline void gsc_hw_enable_control(struct gsc_dev *dev, bool on)
+{
+ u32 cfg = readl(dev->regs + GSC_ENABLE);
+
+ if (on)
+ cfg |= GSC_ENABLE_ON;
+ else
+ cfg &= ~GSC_ENABLE_ON;
+
+ writel(cfg, dev->regs + GSC_ENABLE);
+}
+
+static inline int gsc_hw_get_irq_status(struct gsc_dev *dev)
+{
+ u32 cfg = readl(dev->regs + GSC_IRQ);
+ if (cfg & GSC_IRQ_STATUS_OR_IRQ)
+ return GSC_IRQ_OVERRUN;
+ else
+ return GSC_IRQ_DONE;
+
+}
+
+static inline void gsc_hw_clear_irq(struct gsc_dev *dev, int irq)
+{
+ u32 cfg = readl(dev->regs + GSC_IRQ);
+ if (irq == GSC_IRQ_OVERRUN)
+ cfg |= GSC_IRQ_STATUS_OR_IRQ;
+ else if (irq == GSC_IRQ_DONE)
+ cfg |= GSC_IRQ_STATUS_FRM_DONE_IRQ;
+ writel(cfg, dev->regs + GSC_IRQ);
+}
+
+static inline bool gsc_ctx_state_is_set(u32 mask, struct gsc_ctx *ctx)
+{
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&ctx->gsc_dev->slock, flags);
+ ret = (ctx->state & mask) == mask;
+ spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags);
+ return ret;
+}
+
+static inline struct gsc_frame *ctx_get_frame(struct gsc_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ struct gsc_frame *frame;
+
+ if (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE == type) {
+ frame = &ctx->s_frame;
+ } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == type) {
+ frame = &ctx->d_frame;
+ } else {
+ pr_err("Wrong buffer/video queue type (%d)", type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return frame;
+}
+
+void gsc_hw_set_sw_reset(struct gsc_dev *dev);
+int gsc_wait_reset(struct gsc_dev *dev);
+
+void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask);
+void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask);
+void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift, bool enable);
+void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift, bool enable);
+void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr,
+ int index);
+void gsc_hw_set_output_addr(struct gsc_dev *dev, struct gsc_addr *addr,
+ int index);
+void gsc_hw_set_input_path(struct gsc_ctx *ctx);
+void gsc_hw_set_in_size(struct gsc_ctx *ctx);
+void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx);
+void gsc_hw_set_in_image_format(struct gsc_ctx *ctx);
+void gsc_hw_set_output_path(struct gsc_ctx *ctx);
+void gsc_hw_set_out_size(struct gsc_ctx *ctx);
+void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx);
+void gsc_hw_set_out_image_format(struct gsc_ctx *ctx);
+void gsc_hw_set_prescaler(struct gsc_ctx *ctx);
+void gsc_hw_set_mainscaler(struct gsc_ctx *ctx);
+void gsc_hw_set_rotation(struct gsc_ctx *ctx);
+void gsc_hw_set_global_alpha(struct gsc_ctx *ctx);
+void gsc_hw_set_sfr_update(struct gsc_ctx *ctx);
+
+#endif /* GSC_CORE_H_ */
diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c b/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c
new file mode 100644
index 0000000000..b7854ce5fb
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c
@@ -0,0 +1,789 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Samsung EXYNOS5 SoC series G-Scaler driver
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include <media/v4l2-ioctl.h>
+
+#include "gsc-core.h"
+
+static int gsc_m2m_ctx_stop_req(struct gsc_ctx *ctx)
+{
+ struct gsc_ctx *curr_ctx;
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ int ret;
+
+ curr_ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev);
+ if (!gsc_m2m_pending(gsc) || (curr_ctx != ctx))
+ return 0;
+
+ gsc_ctx_state_lock_set(GSC_CTX_STOP_REQ, ctx);
+ ret = wait_event_timeout(gsc->irq_queue,
+ !gsc_ctx_state_is_set(GSC_CTX_STOP_REQ, ctx),
+ GSC_SHUTDOWN_TIMEOUT);
+
+ return ret == 0 ? -ETIMEDOUT : ret;
+}
+
+static void __gsc_m2m_job_abort(struct gsc_ctx *ctx)
+{
+ int ret;
+
+ ret = gsc_m2m_ctx_stop_req(ctx);
+ if ((ret == -ETIMEDOUT) || (ctx->state & GSC_CTX_ABORT)) {
+ gsc_ctx_state_lock_clear(GSC_CTX_STOP_REQ | GSC_CTX_ABORT, ctx);
+ gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
+ }
+}
+
+static int gsc_m2m_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct gsc_ctx *ctx = q->drv_priv;
+
+ return pm_runtime_resume_and_get(&ctx->gsc_dev->pdev->dev);
+}
+
+static void __gsc_m2m_cleanup_queue(struct gsc_ctx *ctx)
+{
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+
+ while (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) > 0) {
+ src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
+ v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_ERROR);
+ }
+
+ while (v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) > 0) {
+ dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
+ v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR);
+ }
+}
+
+static void gsc_m2m_stop_streaming(struct vb2_queue *q)
+{
+ struct gsc_ctx *ctx = q->drv_priv;
+
+ __gsc_m2m_job_abort(ctx);
+
+ __gsc_m2m_cleanup_queue(ctx);
+
+ pm_runtime_put(&ctx->gsc_dev->pdev->dev);
+}
+
+void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state)
+{
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+
+ if (!ctx || !ctx->m2m_ctx)
+ return;
+
+ src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
+ dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
+
+ if (src_vb && dst_vb) {
+ dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp;
+ dst_vb->timecode = src_vb->timecode;
+ dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ dst_vb->flags |=
+ src_vb->flags
+ & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+
+ v4l2_m2m_buf_done(src_vb, vb_state);
+ v4l2_m2m_buf_done(dst_vb, vb_state);
+
+ v4l2_m2m_job_finish(ctx->gsc_dev->m2m.m2m_dev,
+ ctx->m2m_ctx);
+ }
+}
+
+static void gsc_m2m_job_abort(void *priv)
+{
+ __gsc_m2m_job_abort((struct gsc_ctx *)priv);
+}
+
+static int gsc_get_bufs(struct gsc_ctx *ctx)
+{
+ struct gsc_frame *s_frame, *d_frame;
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+ int ret;
+
+ s_frame = &ctx->s_frame;
+ d_frame = &ctx->d_frame;
+
+ src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
+ ret = gsc_prepare_addr(ctx, &src_vb->vb2_buf, s_frame, &s_frame->addr);
+ if (ret)
+ return ret;
+
+ dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+ ret = gsc_prepare_addr(ctx, &dst_vb->vb2_buf, d_frame, &d_frame->addr);
+ if (ret)
+ return ret;
+
+ dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp;
+
+ return 0;
+}
+
+static void gsc_m2m_device_run(void *priv)
+{
+ struct gsc_ctx *ctx = priv;
+ struct gsc_dev *gsc;
+ unsigned long flags;
+ int ret;
+ bool is_set = false;
+
+ if (WARN(!ctx, "null hardware context\n"))
+ return;
+
+ gsc = ctx->gsc_dev;
+ spin_lock_irqsave(&gsc->slock, flags);
+
+ set_bit(ST_M2M_PEND, &gsc->state);
+
+ /* Reconfigure hardware if the context has changed. */
+ if (gsc->m2m.ctx != ctx) {
+ pr_debug("gsc->m2m.ctx = 0x%p, current_ctx = 0x%p",
+ gsc->m2m.ctx, ctx);
+ ctx->state |= GSC_PARAMS;
+ gsc->m2m.ctx = ctx;
+ }
+
+ is_set = ctx->state & GSC_CTX_STOP_REQ;
+ if (is_set) {
+ ctx->state &= ~GSC_CTX_STOP_REQ;
+ ctx->state |= GSC_CTX_ABORT;
+ wake_up(&gsc->irq_queue);
+ goto put_device;
+ }
+
+ ret = gsc_get_bufs(ctx);
+ if (ret) {
+ pr_err("Wrong address");
+ goto put_device;
+ }
+
+ gsc_set_prefbuf(gsc, &ctx->s_frame);
+ gsc_hw_set_input_addr(gsc, &ctx->s_frame.addr, GSC_M2M_BUF_NUM);
+ gsc_hw_set_output_addr(gsc, &ctx->d_frame.addr, GSC_M2M_BUF_NUM);
+
+ if (ctx->state & GSC_PARAMS) {
+ gsc_hw_set_input_buf_masking(gsc, GSC_M2M_BUF_NUM, false);
+ gsc_hw_set_output_buf_masking(gsc, GSC_M2M_BUF_NUM, false);
+ gsc_hw_set_frm_done_irq_mask(gsc, false);
+ gsc_hw_set_gsc_irq_enable(gsc, true);
+
+ if (gsc_set_scaler_info(ctx)) {
+ pr_err("Scaler setup error");
+ goto put_device;
+ }
+
+ gsc_hw_set_input_path(ctx);
+ gsc_hw_set_in_size(ctx);
+ gsc_hw_set_in_image_format(ctx);
+
+ gsc_hw_set_output_path(ctx);
+ gsc_hw_set_out_size(ctx);
+ gsc_hw_set_out_image_format(ctx);
+
+ gsc_hw_set_prescaler(ctx);
+ gsc_hw_set_mainscaler(ctx);
+ gsc_hw_set_rotation(ctx);
+ gsc_hw_set_global_alpha(ctx);
+ }
+
+ /* update shadow registers */
+ gsc_hw_set_sfr_update(ctx);
+
+ ctx->state &= ~GSC_PARAMS;
+ gsc_hw_enable_control(gsc, true);
+
+ spin_unlock_irqrestore(&gsc->slock, flags);
+ return;
+
+put_device:
+ ctx->state &= ~GSC_PARAMS;
+ spin_unlock_irqrestore(&gsc->slock, flags);
+}
+
+static int gsc_m2m_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct gsc_ctx *ctx = vb2_get_drv_priv(vq);
+ struct gsc_frame *frame;
+ int i;
+
+ frame = ctx_get_frame(ctx, vq->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ if (!frame->fmt)
+ return -EINVAL;
+
+ *num_planes = frame->fmt->num_planes;
+ for (i = 0; i < frame->fmt->num_planes; i++)
+ sizes[i] = frame->payload[i];
+ return 0;
+}
+
+static int gsc_m2m_buf_prepare(struct vb2_buffer *vb)
+{
+ struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct gsc_frame *frame;
+ int i;
+
+ frame = ctx_get_frame(ctx, vb->vb2_queue->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) {
+ for (i = 0; i < frame->fmt->num_planes; i++)
+ vb2_set_plane_payload(vb, i, frame->payload[i]);
+ }
+
+ return 0;
+}
+
+static void gsc_m2m_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ pr_debug("ctx: %p, ctx->state: 0x%x", ctx, ctx->state);
+
+ if (ctx->m2m_ctx)
+ v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf);
+}
+
+static const struct vb2_ops gsc_m2m_qops = {
+ .queue_setup = gsc_m2m_queue_setup,
+ .buf_prepare = gsc_m2m_buf_prepare,
+ .buf_queue = gsc_m2m_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = gsc_m2m_stop_streaming,
+ .start_streaming = gsc_m2m_start_streaming,
+};
+
+static int gsc_m2m_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, GSC_MODULE_NAME, sizeof(cap->driver));
+ strscpy(cap->card, GSC_MODULE_NAME " gscaler", sizeof(cap->card));
+ return 0;
+}
+
+static int gsc_m2m_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return gsc_enum_fmt(f);
+}
+
+static int gsc_m2m_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+
+ return gsc_g_fmt_mplane(ctx, f);
+}
+
+static int gsc_m2m_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+
+ return gsc_try_fmt_mplane(ctx, f);
+}
+
+static int gsc_m2m_s_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ struct vb2_queue *vq;
+ struct gsc_frame *frame;
+ struct v4l2_pix_format_mplane *pix;
+ int i, ret = 0;
+
+ ret = gsc_m2m_try_fmt_mplane(file, fh, f);
+ if (ret)
+ return ret;
+
+ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
+
+ if (vb2_is_streaming(vq)) {
+ pr_err("queue (%d) busy", f->type);
+ return -EBUSY;
+ }
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type))
+ frame = &ctx->s_frame;
+ else
+ frame = &ctx->d_frame;
+
+ pix = &f->fmt.pix_mp;
+ frame->fmt = find_fmt(&pix->pixelformat, NULL, 0);
+ frame->colorspace = pix->colorspace;
+ if (!frame->fmt)
+ return -EINVAL;
+
+ for (i = 0; i < frame->fmt->num_planes; i++)
+ frame->payload[i] = pix->plane_fmt[i].sizeimage;
+
+ gsc_set_frame_size(frame, pix->width, pix->height);
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ gsc_ctx_state_lock_set(GSC_PARAMS | GSC_DST_FMT, ctx);
+ else
+ gsc_ctx_state_lock_set(GSC_PARAMS | GSC_SRC_FMT, ctx);
+
+ pr_debug("f_w: %d, f_h: %d", frame->f_width, frame->f_height);
+
+ return 0;
+}
+
+static int gsc_m2m_reqbufs(struct file *file, void *fh,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ u32 max_cnt;
+
+ max_cnt = (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ?
+ gsc->variant->in_buf_cnt : gsc->variant->out_buf_cnt;
+ if (reqbufs->count > max_cnt)
+ return -EINVAL;
+
+ return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs);
+}
+
+static int gsc_m2m_expbuf(struct file *file, void *fh,
+ struct v4l2_exportbuffer *eb)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb);
+}
+
+static int gsc_m2m_querybuf(struct file *file, void *fh,
+ struct v4l2_buffer *buf)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf);
+}
+
+static int gsc_m2m_qbuf(struct file *file, void *fh,
+ struct v4l2_buffer *buf)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf);
+}
+
+static int gsc_m2m_dqbuf(struct file *file, void *fh,
+ struct v4l2_buffer *buf)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf);
+}
+
+static int gsc_m2m_streamon(struct file *file, void *fh,
+ enum v4l2_buf_type type)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+
+ /* The source and target color format need to be set */
+ if (V4L2_TYPE_IS_OUTPUT(type)) {
+ if (!gsc_ctx_state_is_set(GSC_SRC_FMT, ctx))
+ return -EINVAL;
+ } else if (!gsc_ctx_state_is_set(GSC_DST_FMT, ctx)) {
+ return -EINVAL;
+ }
+
+ return v4l2_m2m_streamon(file, ctx->m2m_ctx, type);
+}
+
+static int gsc_m2m_streamoff(struct file *file, void *fh,
+ enum v4l2_buf_type type)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type);
+}
+
+/* Return 1 if rectangle a is enclosed in rectangle b, or 0 otherwise. */
+static int is_rectangle_enclosed(struct v4l2_rect *a, struct v4l2_rect *b)
+{
+ if (a->left < b->left || a->top < b->top)
+ return 0;
+
+ if (a->left + a->width > b->left + b->width)
+ return 0;
+
+ if (a->top + a->height > b->top + b->height)
+ return 0;
+
+ return 1;
+}
+
+static int gsc_m2m_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct gsc_frame *frame;
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+
+ if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
+ (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT))
+ return -EINVAL;
+
+ frame = ctx_get_frame(ctx, s->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = frame->f_width;
+ s->r.height = frame->f_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_CROP:
+ s->r.left = frame->crop.left;
+ s->r.top = frame->crop.top;
+ s->r.width = frame->crop.width;
+ s->r.height = frame->crop.height;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int gsc_m2m_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct gsc_frame *frame;
+ struct gsc_ctx *ctx = fh_to_ctx(fh);
+ struct gsc_variant *variant = ctx->gsc_dev->variant;
+ struct v4l2_selection sel = *s;
+ int ret;
+
+ if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) &&
+ (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT))
+ return -EINVAL;
+
+ ret = gsc_try_selection(ctx, &sel);
+ if (ret)
+ return ret;
+
+ if (s->flags & V4L2_SEL_FLAG_LE &&
+ !is_rectangle_enclosed(&sel.r, &s->r))
+ return -ERANGE;
+
+ if (s->flags & V4L2_SEL_FLAG_GE &&
+ !is_rectangle_enclosed(&s->r, &sel.r))
+ return -ERANGE;
+
+ s->r = sel.r;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE:
+ frame = &ctx->s_frame;
+ break;
+
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ frame = &ctx->d_frame;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Check to see if scaling ratio is within supported range */
+ if (gsc_ctx_state_is_set(GSC_DST_FMT | GSC_SRC_FMT, ctx)) {
+ if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = gsc_check_scaler_ratio(variant, sel.r.width,
+ sel.r.height, ctx->d_frame.crop.width,
+ ctx->d_frame.crop.height,
+ ctx->gsc_ctrls.rotate->val, ctx->out_path);
+ } else {
+ ret = gsc_check_scaler_ratio(variant,
+ ctx->s_frame.crop.width,
+ ctx->s_frame.crop.height, sel.r.width,
+ sel.r.height, ctx->gsc_ctrls.rotate->val,
+ ctx->out_path);
+ }
+
+ if (ret) {
+ pr_err("Out of scaler range");
+ return -EINVAL;
+ }
+ }
+
+ frame->crop = sel.r;
+
+ gsc_ctx_state_lock_set(GSC_PARAMS, ctx);
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops gsc_m2m_ioctl_ops = {
+ .vidioc_querycap = gsc_m2m_querycap,
+ .vidioc_enum_fmt_vid_cap = gsc_m2m_enum_fmt,
+ .vidioc_enum_fmt_vid_out = gsc_m2m_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = gsc_m2m_g_fmt_mplane,
+ .vidioc_g_fmt_vid_out_mplane = gsc_m2m_g_fmt_mplane,
+ .vidioc_try_fmt_vid_cap_mplane = gsc_m2m_try_fmt_mplane,
+ .vidioc_try_fmt_vid_out_mplane = gsc_m2m_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = gsc_m2m_s_fmt_mplane,
+ .vidioc_s_fmt_vid_out_mplane = gsc_m2m_s_fmt_mplane,
+ .vidioc_reqbufs = gsc_m2m_reqbufs,
+ .vidioc_expbuf = gsc_m2m_expbuf,
+ .vidioc_querybuf = gsc_m2m_querybuf,
+ .vidioc_qbuf = gsc_m2m_qbuf,
+ .vidioc_dqbuf = gsc_m2m_dqbuf,
+ .vidioc_streamon = gsc_m2m_streamon,
+ .vidioc_streamoff = gsc_m2m_streamoff,
+ .vidioc_g_selection = gsc_m2m_g_selection,
+ .vidioc_s_selection = gsc_m2m_s_selection
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct gsc_ctx *ctx = priv;
+ int ret;
+
+ memset(src_vq, 0, sizeof(*src_vq));
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &gsc_m2m_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->gsc_dev->lock;
+ src_vq->dev = &ctx->gsc_dev->pdev->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ memset(dst_vq, 0, sizeof(*dst_vq));
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->ops = &gsc_m2m_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->gsc_dev->lock;
+ dst_vq->dev = &ctx->gsc_dev->pdev->dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+static int gsc_m2m_open(struct file *file)
+{
+ struct gsc_dev *gsc = video_drvdata(file);
+ struct gsc_ctx *ctx = NULL;
+ int ret;
+
+ pr_debug("pid: %d, state: 0x%lx", task_pid_nr(current), gsc->state);
+
+ if (mutex_lock_interruptible(&gsc->lock))
+ return -ERESTARTSYS;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ v4l2_fh_init(&ctx->fh, gsc->m2m.vfd);
+ ret = gsc_ctrls_create(ctx);
+ if (ret)
+ goto error_fh;
+
+ /* Use separate control handler per file handle */
+ ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+
+ ctx->gsc_dev = gsc;
+ /* Default color format */
+ ctx->s_frame.fmt = get_format(0);
+ ctx->d_frame.fmt = get_format(0);
+ /* Setup the device context for mem2mem mode. */
+ ctx->state = GSC_CTX_M2M;
+ ctx->flags = 0;
+ ctx->in_path = GSC_DMA;
+ ctx->out_path = GSC_DMA;
+
+ ctx->m2m_ctx = v4l2_m2m_ctx_init(gsc->m2m.m2m_dev, ctx, queue_init);
+ if (IS_ERR(ctx->m2m_ctx)) {
+ pr_err("Failed to initialize m2m context");
+ ret = PTR_ERR(ctx->m2m_ctx);
+ goto error_ctrls;
+ }
+
+ if (gsc->m2m.refcnt++ == 0)
+ set_bit(ST_M2M_OPEN, &gsc->state);
+
+ pr_debug("gsc m2m driver is opened, ctx(0x%p)", ctx);
+
+ mutex_unlock(&gsc->lock);
+ return 0;
+
+error_ctrls:
+ gsc_ctrls_delete(ctx);
+ v4l2_fh_del(&ctx->fh);
+error_fh:
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+unlock:
+ mutex_unlock(&gsc->lock);
+ return ret;
+}
+
+static int gsc_m2m_release(struct file *file)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct gsc_dev *gsc = ctx->gsc_dev;
+
+ pr_debug("pid: %d, state: 0x%lx, refcnt= %d",
+ task_pid_nr(current), gsc->state, gsc->m2m.refcnt);
+
+ mutex_lock(&gsc->lock);
+
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+ gsc_ctrls_delete(ctx);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+
+ if (--gsc->m2m.refcnt <= 0)
+ clear_bit(ST_M2M_OPEN, &gsc->state);
+ kfree(ctx);
+
+ mutex_unlock(&gsc->lock);
+ return 0;
+}
+
+static __poll_t gsc_m2m_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ __poll_t ret;
+
+ if (mutex_lock_interruptible(&gsc->lock))
+ return EPOLLERR;
+
+ ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait);
+ mutex_unlock(&gsc->lock);
+
+ return ret;
+}
+
+static int gsc_m2m_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct gsc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct gsc_dev *gsc = ctx->gsc_dev;
+ int ret;
+
+ if (mutex_lock_interruptible(&gsc->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma);
+ mutex_unlock(&gsc->lock);
+
+ return ret;
+}
+
+static const struct v4l2_file_operations gsc_m2m_fops = {
+ .owner = THIS_MODULE,
+ .open = gsc_m2m_open,
+ .release = gsc_m2m_release,
+ .poll = gsc_m2m_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = gsc_m2m_mmap,
+};
+
+static const struct v4l2_m2m_ops gsc_m2m_ops = {
+ .device_run = gsc_m2m_device_run,
+ .job_abort = gsc_m2m_job_abort,
+};
+
+int gsc_register_m2m_device(struct gsc_dev *gsc)
+{
+ struct platform_device *pdev;
+ int ret;
+
+ if (!gsc)
+ return -ENODEV;
+
+ pdev = gsc->pdev;
+
+ gsc->vdev.fops = &gsc_m2m_fops;
+ gsc->vdev.ioctl_ops = &gsc_m2m_ioctl_ops;
+ gsc->vdev.release = video_device_release_empty;
+ gsc->vdev.lock = &gsc->lock;
+ gsc->vdev.vfl_dir = VFL_DIR_M2M;
+ gsc->vdev.v4l2_dev = &gsc->v4l2_dev;
+ gsc->vdev.device_caps = V4L2_CAP_STREAMING |
+ V4L2_CAP_VIDEO_M2M_MPLANE;
+ snprintf(gsc->vdev.name, sizeof(gsc->vdev.name), "%s.%d:m2m",
+ GSC_MODULE_NAME, gsc->id);
+
+ video_set_drvdata(&gsc->vdev, gsc);
+
+ gsc->m2m.vfd = &gsc->vdev;
+ gsc->m2m.m2m_dev = v4l2_m2m_init(&gsc_m2m_ops);
+ if (IS_ERR(gsc->m2m.m2m_dev)) {
+ dev_err(&pdev->dev, "failed to initialize v4l2-m2m device\n");
+ return PTR_ERR(gsc->m2m.m2m_dev);
+ }
+
+ ret = video_register_device(&gsc->vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "%s(): failed to register video device\n", __func__);
+ goto err_m2m_release;
+ }
+
+ pr_debug("gsc m2m driver registered as /dev/video%d", gsc->vdev.num);
+ return 0;
+
+err_m2m_release:
+ v4l2_m2m_release(gsc->m2m.m2m_dev);
+
+ return ret;
+}
+
+void gsc_unregister_m2m_device(struct gsc_dev *gsc)
+{
+ if (gsc) {
+ v4l2_m2m_release(gsc->m2m.m2m_dev);
+ video_unregister_device(&gsc->vdev);
+ }
+}
diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c
new file mode 100644
index 0000000000..995a1f0f87
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Samsung EXYNOS5 SoC series G-Scaler driver
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include "gsc-core.h"
+
+void gsc_hw_set_sw_reset(struct gsc_dev *dev)
+{
+ writel(GSC_SW_RESET_SRESET, dev->regs + GSC_SW_RESET);
+}
+
+int gsc_wait_reset(struct gsc_dev *dev)
+{
+ unsigned long end = jiffies + msecs_to_jiffies(50);
+ u32 cfg;
+
+ while (time_before(jiffies, end)) {
+ cfg = readl(dev->regs + GSC_SW_RESET);
+ if (!cfg)
+ return 0;
+ usleep_range(10, 20);
+ }
+
+ return -EBUSY;
+}
+
+void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask)
+{
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_IRQ);
+ if (mask)
+ cfg |= GSC_IRQ_FRMDONE_MASK;
+ else
+ cfg &= ~GSC_IRQ_FRMDONE_MASK;
+ writel(cfg, dev->regs + GSC_IRQ);
+}
+
+void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask)
+{
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_IRQ);
+ if (mask)
+ cfg |= GSC_IRQ_ENABLE;
+ else
+ cfg &= ~GSC_IRQ_ENABLE;
+ writel(cfg, dev->regs + GSC_IRQ);
+}
+
+void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift,
+ bool enable)
+{
+ u32 cfg = readl(dev->regs + GSC_IN_BASE_ADDR_Y_MASK);
+ u32 mask = 1 << shift;
+
+ cfg &= ~mask;
+ cfg |= enable << shift;
+
+ writel(cfg, dev->regs + GSC_IN_BASE_ADDR_Y_MASK);
+ writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CB_MASK);
+ writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CR_MASK);
+}
+
+void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift,
+ bool enable)
+{
+ u32 cfg = readl(dev->regs + GSC_OUT_BASE_ADDR_Y_MASK);
+ u32 mask = 1 << shift;
+
+ cfg &= ~mask;
+ cfg |= enable << shift;
+
+ writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_Y_MASK);
+ writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CB_MASK);
+ writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CR_MASK);
+}
+
+void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr,
+ int index)
+{
+ pr_debug("src_buf[%d]: %pad, cb: %pad, cr: %pad", index,
+ &addr->y, &addr->cb, &addr->cr);
+ writel(addr->y, dev->regs + GSC_IN_BASE_ADDR_Y(index));
+ writel(addr->cb, dev->regs + GSC_IN_BASE_ADDR_CB(index));
+ writel(addr->cr, dev->regs + GSC_IN_BASE_ADDR_CR(index));
+
+}
+
+void gsc_hw_set_output_addr(struct gsc_dev *dev,
+ struct gsc_addr *addr, int index)
+{
+ pr_debug("dst_buf[%d]: %pad, cb: %pad, cr: %pad",
+ index, &addr->y, &addr->cb, &addr->cr);
+ writel(addr->y, dev->regs + GSC_OUT_BASE_ADDR_Y(index));
+ writel(addr->cb, dev->regs + GSC_OUT_BASE_ADDR_CB(index));
+ writel(addr->cr, dev->regs + GSC_OUT_BASE_ADDR_CR(index));
+}
+
+void gsc_hw_set_input_path(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+
+ u32 cfg = readl(dev->regs + GSC_IN_CON);
+ cfg &= ~(GSC_IN_PATH_MASK | GSC_IN_LOCAL_SEL_MASK);
+
+ if (ctx->in_path == GSC_DMA)
+ cfg |= GSC_IN_PATH_MEMORY;
+
+ writel(cfg, dev->regs + GSC_IN_CON);
+}
+
+void gsc_hw_set_in_size(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->s_frame;
+ u32 cfg;
+
+ /* Set input pixel offset */
+ cfg = GSC_SRCIMG_OFFSET_X(frame->crop.left);
+ cfg |= GSC_SRCIMG_OFFSET_Y(frame->crop.top);
+ writel(cfg, dev->regs + GSC_SRCIMG_OFFSET);
+
+ /* Set input original size */
+ cfg = GSC_SRCIMG_WIDTH(frame->f_width);
+ cfg |= GSC_SRCIMG_HEIGHT(frame->f_height);
+ writel(cfg, dev->regs + GSC_SRCIMG_SIZE);
+
+ /* Set input cropped size */
+ cfg = GSC_CROPPED_WIDTH(frame->crop.width);
+ cfg |= GSC_CROPPED_HEIGHT(frame->crop.height);
+ writel(cfg, dev->regs + GSC_CROPPED_SIZE);
+}
+
+void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->s_frame;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_IN_CON);
+ if (frame->colorspace == V4L2_COLORSPACE_REC709)
+ cfg |= GSC_IN_RGB_HD_WIDE;
+ else
+ cfg |= GSC_IN_RGB_SD_WIDE;
+
+ if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X)
+ cfg |= GSC_IN_RGB565;
+ else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32)
+ cfg |= GSC_IN_XRGB8888;
+
+ writel(cfg, dev->regs + GSC_IN_CON);
+}
+
+void gsc_hw_set_in_image_format(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->s_frame;
+ u32 i, depth = 0;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_IN_CON);
+ cfg &= ~(GSC_IN_RGB_TYPE_MASK | GSC_IN_YUV422_1P_ORDER_MASK |
+ GSC_IN_CHROMA_ORDER_MASK | GSC_IN_FORMAT_MASK |
+ GSC_IN_TILE_TYPE_MASK | GSC_IN_TILE_MODE);
+ writel(cfg, dev->regs + GSC_IN_CON);
+
+ if (is_rgb(frame->fmt->color)) {
+ gsc_hw_set_in_image_rgb(ctx);
+ return;
+ }
+ for (i = 0; i < frame->fmt->num_planes; i++)
+ depth += frame->fmt->depth[i];
+
+ switch (frame->fmt->num_comp) {
+ case 1:
+ cfg |= GSC_IN_YUV422_1P;
+ if (frame->fmt->yorder == GSC_LSB_Y)
+ cfg |= GSC_IN_YUV422_1P_ORDER_LSB_Y;
+ else
+ cfg |= GSC_IN_YUV422_1P_OEDER_LSB_C;
+ if (frame->fmt->corder == GSC_CBCR)
+ cfg |= GSC_IN_CHROMA_ORDER_CBCR;
+ else
+ cfg |= GSC_IN_CHROMA_ORDER_CRCB;
+ break;
+ case 2:
+ if (depth == 12)
+ cfg |= GSC_IN_YUV420_2P;
+ else
+ cfg |= GSC_IN_YUV422_2P;
+ if (frame->fmt->corder == GSC_CBCR)
+ cfg |= GSC_IN_CHROMA_ORDER_CBCR;
+ else
+ cfg |= GSC_IN_CHROMA_ORDER_CRCB;
+ break;
+ case 3:
+ if (depth == 12)
+ cfg |= GSC_IN_YUV420_3P;
+ else
+ cfg |= GSC_IN_YUV422_3P;
+ break;
+ }
+
+ if (is_tiled(frame->fmt))
+ cfg |= GSC_IN_TILE_C_16x8 | GSC_IN_TILE_MODE;
+
+ writel(cfg, dev->regs + GSC_IN_CON);
+}
+
+void gsc_hw_set_output_path(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+
+ u32 cfg = readl(dev->regs + GSC_OUT_CON);
+ cfg &= ~GSC_OUT_PATH_MASK;
+
+ if (ctx->out_path == GSC_DMA)
+ cfg |= GSC_OUT_PATH_MEMORY;
+ else
+ cfg |= GSC_OUT_PATH_LOCAL;
+
+ writel(cfg, dev->regs + GSC_OUT_CON);
+}
+
+void gsc_hw_set_out_size(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->d_frame;
+ u32 cfg;
+
+ /* Set output original size */
+ if (ctx->out_path == GSC_DMA) {
+ cfg = GSC_DSTIMG_OFFSET_X(frame->crop.left);
+ cfg |= GSC_DSTIMG_OFFSET_Y(frame->crop.top);
+ writel(cfg, dev->regs + GSC_DSTIMG_OFFSET);
+
+ cfg = GSC_DSTIMG_WIDTH(frame->f_width);
+ cfg |= GSC_DSTIMG_HEIGHT(frame->f_height);
+ writel(cfg, dev->regs + GSC_DSTIMG_SIZE);
+ }
+
+ /* Set output scaled size */
+ if (ctx->gsc_ctrls.rotate->val == 90 ||
+ ctx->gsc_ctrls.rotate->val == 270) {
+ cfg = GSC_SCALED_WIDTH(frame->crop.height);
+ cfg |= GSC_SCALED_HEIGHT(frame->crop.width);
+ } else {
+ cfg = GSC_SCALED_WIDTH(frame->crop.width);
+ cfg |= GSC_SCALED_HEIGHT(frame->crop.height);
+ }
+ writel(cfg, dev->regs + GSC_SCALED_SIZE);
+}
+
+void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->d_frame;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_OUT_CON);
+ if (frame->colorspace == V4L2_COLORSPACE_REC709)
+ cfg |= GSC_OUT_RGB_HD_WIDE;
+ else
+ cfg |= GSC_OUT_RGB_SD_WIDE;
+
+ if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X)
+ cfg |= GSC_OUT_RGB565;
+ else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32)
+ cfg |= GSC_OUT_XRGB8888;
+
+ writel(cfg, dev->regs + GSC_OUT_CON);
+}
+
+void gsc_hw_set_out_image_format(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->d_frame;
+ u32 i, depth = 0;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_OUT_CON);
+ cfg &= ~(GSC_OUT_RGB_TYPE_MASK | GSC_OUT_YUV422_1P_ORDER_MASK |
+ GSC_OUT_CHROMA_ORDER_MASK | GSC_OUT_FORMAT_MASK |
+ GSC_OUT_TILE_TYPE_MASK | GSC_OUT_TILE_MODE);
+ writel(cfg, dev->regs + GSC_OUT_CON);
+
+ if (is_rgb(frame->fmt->color)) {
+ gsc_hw_set_out_image_rgb(ctx);
+ return;
+ }
+
+ if (ctx->out_path != GSC_DMA) {
+ cfg |= GSC_OUT_YUV444;
+ goto end_set;
+ }
+
+ for (i = 0; i < frame->fmt->num_planes; i++)
+ depth += frame->fmt->depth[i];
+
+ switch (frame->fmt->num_comp) {
+ case 1:
+ cfg |= GSC_OUT_YUV422_1P;
+ if (frame->fmt->yorder == GSC_LSB_Y)
+ cfg |= GSC_OUT_YUV422_1P_ORDER_LSB_Y;
+ else
+ cfg |= GSC_OUT_YUV422_1P_OEDER_LSB_C;
+ if (frame->fmt->corder == GSC_CBCR)
+ cfg |= GSC_OUT_CHROMA_ORDER_CBCR;
+ else
+ cfg |= GSC_OUT_CHROMA_ORDER_CRCB;
+ break;
+ case 2:
+ if (depth == 12)
+ cfg |= GSC_OUT_YUV420_2P;
+ else
+ cfg |= GSC_OUT_YUV422_2P;
+ if (frame->fmt->corder == GSC_CBCR)
+ cfg |= GSC_OUT_CHROMA_ORDER_CBCR;
+ else
+ cfg |= GSC_OUT_CHROMA_ORDER_CRCB;
+ break;
+ case 3:
+ cfg |= GSC_OUT_YUV420_3P;
+ break;
+ }
+
+ if (is_tiled(frame->fmt))
+ cfg |= GSC_OUT_TILE_C_16x8 | GSC_OUT_TILE_MODE;
+
+end_set:
+ writel(cfg, dev->regs + GSC_OUT_CON);
+}
+
+void gsc_hw_set_prescaler(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_scaler *sc = &ctx->scaler;
+ u32 cfg;
+
+ cfg = GSC_PRESC_SHFACTOR(sc->pre_shfactor);
+ cfg |= GSC_PRESC_H_RATIO(sc->pre_hratio);
+ cfg |= GSC_PRESC_V_RATIO(sc->pre_vratio);
+ writel(cfg, dev->regs + GSC_PRE_SCALE_RATIO);
+}
+
+void gsc_hw_set_mainscaler(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_scaler *sc = &ctx->scaler;
+ u32 cfg;
+
+ cfg = GSC_MAIN_H_RATIO_VALUE(sc->main_hratio);
+ writel(cfg, dev->regs + GSC_MAIN_H_RATIO);
+
+ cfg = GSC_MAIN_V_RATIO_VALUE(sc->main_vratio);
+ writel(cfg, dev->regs + GSC_MAIN_V_RATIO);
+}
+
+void gsc_hw_set_rotation(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_IN_CON);
+ cfg &= ~GSC_IN_ROT_MASK;
+
+ switch (ctx->gsc_ctrls.rotate->val) {
+ case 270:
+ cfg |= GSC_IN_ROT_270;
+ break;
+ case 180:
+ cfg |= GSC_IN_ROT_180;
+ break;
+ case 90:
+ if (ctx->gsc_ctrls.hflip->val)
+ cfg |= GSC_IN_ROT_90_XFLIP;
+ else if (ctx->gsc_ctrls.vflip->val)
+ cfg |= GSC_IN_ROT_90_YFLIP;
+ else
+ cfg |= GSC_IN_ROT_90;
+ break;
+ case 0:
+ if (ctx->gsc_ctrls.hflip->val)
+ cfg |= GSC_IN_ROT_XFLIP;
+ else if (ctx->gsc_ctrls.vflip->val)
+ cfg |= GSC_IN_ROT_YFLIP;
+ }
+
+ writel(cfg, dev->regs + GSC_IN_CON);
+}
+
+void gsc_hw_set_global_alpha(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ struct gsc_frame *frame = &ctx->d_frame;
+ u32 cfg;
+
+ if (!is_rgb(frame->fmt->color)) {
+ pr_debug("Not a RGB format");
+ return;
+ }
+
+ cfg = readl(dev->regs + GSC_OUT_CON);
+ cfg &= ~GSC_OUT_GLOBAL_ALPHA_MASK;
+
+ cfg |= GSC_OUT_GLOBAL_ALPHA(ctx->gsc_ctrls.global_alpha->val);
+ writel(cfg, dev->regs + GSC_OUT_CON);
+}
+
+void gsc_hw_set_sfr_update(struct gsc_ctx *ctx)
+{
+ struct gsc_dev *dev = ctx->gsc_dev;
+ u32 cfg;
+
+ cfg = readl(dev->regs + GSC_ENABLE);
+ cfg |= GSC_ENABLE_SFR_UPDATE;
+ writel(cfg, dev->regs + GSC_ENABLE);
+}
diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h
new file mode 100644
index 0000000000..d4f7ead6b3
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Register definition file for Samsung G-Scaler driver
+ */
+
+#ifndef REGS_GSC_H_
+#define REGS_GSC_H_
+
+/* G-Scaler enable */
+#define GSC_ENABLE 0x00
+#define GSC_ENABLE_OP_STATUS (1 << 2)
+#define GSC_ENABLE_SFR_UPDATE (1 << 1)
+#define GSC_ENABLE_ON (1 << 0)
+
+/* G-Scaler S/W reset */
+#define GSC_SW_RESET 0x04
+#define GSC_SW_RESET_SRESET (1 << 0)
+
+/* G-Scaler IRQ */
+#define GSC_IRQ 0x08
+#define GSC_IRQ_STATUS_OR_IRQ (1 << 17)
+#define GSC_IRQ_STATUS_FRM_DONE_IRQ (1 << 16)
+#define GSC_IRQ_FRMDONE_MASK (1 << 1)
+#define GSC_IRQ_ENABLE (1 << 0)
+
+/* G-Scaler input control */
+#define GSC_IN_CON 0x10
+#define GSC_IN_ROT_MASK (7 << 16)
+#define GSC_IN_ROT_270 (7 << 16)
+#define GSC_IN_ROT_90_YFLIP (6 << 16)
+#define GSC_IN_ROT_90_XFLIP (5 << 16)
+#define GSC_IN_ROT_90 (4 << 16)
+#define GSC_IN_ROT_180 (3 << 16)
+#define GSC_IN_ROT_YFLIP (2 << 16)
+#define GSC_IN_ROT_XFLIP (1 << 16)
+#define GSC_IN_RGB_TYPE_MASK (3 << 14)
+#define GSC_IN_RGB_HD_NARROW (3 << 14)
+#define GSC_IN_RGB_HD_WIDE (2 << 14)
+#define GSC_IN_RGB_SD_NARROW (1 << 14)
+#define GSC_IN_RGB_SD_WIDE (0 << 14)
+#define GSC_IN_YUV422_1P_ORDER_MASK (1 << 13)
+#define GSC_IN_YUV422_1P_ORDER_LSB_Y (0 << 13)
+#define GSC_IN_YUV422_1P_OEDER_LSB_C (1 << 13)
+#define GSC_IN_CHROMA_ORDER_MASK (1 << 12)
+#define GSC_IN_CHROMA_ORDER_CBCR (0 << 12)
+#define GSC_IN_CHROMA_ORDER_CRCB (1 << 12)
+#define GSC_IN_FORMAT_MASK (7 << 8)
+#define GSC_IN_XRGB8888 (0 << 8)
+#define GSC_IN_RGB565 (1 << 8)
+#define GSC_IN_YUV420_2P (2 << 8)
+#define GSC_IN_YUV420_3P (3 << 8)
+#define GSC_IN_YUV422_1P (4 << 8)
+#define GSC_IN_YUV422_2P (5 << 8)
+#define GSC_IN_YUV422_3P (6 << 8)
+#define GSC_IN_TILE_TYPE_MASK (1 << 4)
+#define GSC_IN_TILE_C_16x8 (0 << 4)
+#define GSC_IN_TILE_MODE (1 << 3)
+#define GSC_IN_LOCAL_SEL_MASK (3 << 1)
+#define GSC_IN_PATH_MASK (1 << 0)
+#define GSC_IN_PATH_MEMORY (0 << 0)
+
+/* G-Scaler source image size */
+#define GSC_SRCIMG_SIZE 0x14
+#define GSC_SRCIMG_HEIGHT(x) ((x) << 16)
+#define GSC_SRCIMG_WIDTH(x) ((x) << 0)
+
+/* G-Scaler source image offset */
+#define GSC_SRCIMG_OFFSET 0x18
+#define GSC_SRCIMG_OFFSET_Y(x) ((x) << 16)
+#define GSC_SRCIMG_OFFSET_X(x) ((x) << 0)
+
+/* G-Scaler cropped source image size */
+#define GSC_CROPPED_SIZE 0x1c
+#define GSC_CROPPED_HEIGHT(x) ((x) << 16)
+#define GSC_CROPPED_WIDTH(x) ((x) << 0)
+
+/* G-Scaler output control */
+#define GSC_OUT_CON 0x20
+#define GSC_OUT_GLOBAL_ALPHA_MASK (0xff << 24)
+#define GSC_OUT_GLOBAL_ALPHA(x) ((x) << 24)
+#define GSC_OUT_RGB_TYPE_MASK (3 << 10)
+#define GSC_OUT_RGB_HD_WIDE (3 << 10)
+#define GSC_OUT_RGB_HD_NARROW (2 << 10)
+#define GSC_OUT_RGB_SD_WIDE (1 << 10)
+#define GSC_OUT_RGB_SD_NARROW (0 << 10)
+#define GSC_OUT_YUV422_1P_ORDER_MASK (1 << 9)
+#define GSC_OUT_YUV422_1P_ORDER_LSB_Y (0 << 9)
+#define GSC_OUT_YUV422_1P_OEDER_LSB_C (1 << 9)
+#define GSC_OUT_CHROMA_ORDER_MASK (1 << 8)
+#define GSC_OUT_CHROMA_ORDER_CBCR (0 << 8)
+#define GSC_OUT_CHROMA_ORDER_CRCB (1 << 8)
+#define GSC_OUT_FORMAT_MASK (7 << 4)
+#define GSC_OUT_XRGB8888 (0 << 4)
+#define GSC_OUT_RGB565 (1 << 4)
+#define GSC_OUT_YUV420_2P (2 << 4)
+#define GSC_OUT_YUV420_3P (3 << 4)
+#define GSC_OUT_YUV422_1P (4 << 4)
+#define GSC_OUT_YUV422_2P (5 << 4)
+#define GSC_OUT_YUV444 (7 << 4)
+#define GSC_OUT_TILE_TYPE_MASK (1 << 2)
+#define GSC_OUT_TILE_C_16x8 (0 << 2)
+#define GSC_OUT_TILE_MODE (1 << 1)
+#define GSC_OUT_PATH_MASK (1 << 0)
+#define GSC_OUT_PATH_LOCAL (1 << 0)
+#define GSC_OUT_PATH_MEMORY (0 << 0)
+
+/* G-Scaler scaled destination image size */
+#define GSC_SCALED_SIZE 0x24
+#define GSC_SCALED_HEIGHT(x) ((x) << 16)
+#define GSC_SCALED_WIDTH(x) ((x) << 0)
+
+/* G-Scaler pre scale ratio */
+#define GSC_PRE_SCALE_RATIO 0x28
+#define GSC_PRESC_SHFACTOR(x) ((x) << 28)
+#define GSC_PRESC_V_RATIO(x) ((x) << 16)
+#define GSC_PRESC_H_RATIO(x) ((x) << 0)
+
+/* G-Scaler main scale horizontal ratio */
+#define GSC_MAIN_H_RATIO 0x2c
+#define GSC_MAIN_H_RATIO_VALUE(x) ((x) << 0)
+
+/* G-Scaler main scale vertical ratio */
+#define GSC_MAIN_V_RATIO 0x30
+#define GSC_MAIN_V_RATIO_VALUE(x) ((x) << 0)
+
+/* G-Scaler destination image size */
+#define GSC_DSTIMG_SIZE 0x40
+#define GSC_DSTIMG_HEIGHT(x) ((x) << 16)
+#define GSC_DSTIMG_WIDTH(x) ((x) << 0)
+
+/* G-Scaler destination image offset */
+#define GSC_DSTIMG_OFFSET 0x44
+#define GSC_DSTIMG_OFFSET_Y(x) ((x) << 16)
+#define GSC_DSTIMG_OFFSET_X(x) ((x) << 0)
+
+/* G-Scaler input y address mask */
+#define GSC_IN_BASE_ADDR_Y_MASK 0x4c
+/* G-Scaler input y base address */
+#define GSC_IN_BASE_ADDR_Y(n) (0x50 + (n) * 0x4)
+
+/* G-Scaler input cb address mask */
+#define GSC_IN_BASE_ADDR_CB_MASK 0x7c
+/* G-Scaler input cb base address */
+#define GSC_IN_BASE_ADDR_CB(n) (0x80 + (n) * 0x4)
+
+/* G-Scaler input cr address mask */
+#define GSC_IN_BASE_ADDR_CR_MASK 0xac
+/* G-Scaler input cr base address */
+#define GSC_IN_BASE_ADDR_CR(n) (0xb0 + (n) * 0x4)
+
+/* G-Scaler output y address mask */
+#define GSC_OUT_BASE_ADDR_Y_MASK 0x10c
+/* G-Scaler output y base address */
+#define GSC_OUT_BASE_ADDR_Y(n) (0x110 + (n) * 0x4)
+
+/* G-Scaler output cb address mask */
+#define GSC_OUT_BASE_ADDR_CB_MASK 0x15c
+/* G-Scaler output cb base address */
+#define GSC_OUT_BASE_ADDR_CB(n) (0x160 + (n) * 0x4)
+
+/* G-Scaler output cr address mask */
+#define GSC_OUT_BASE_ADDR_CR_MASK 0x1ac
+/* G-Scaler output cr base address */
+#define GSC_OUT_BASE_ADDR_CR(n) (0x1b0 + (n) * 0x4)
+
+#endif /* REGS_GSC_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/Kconfig b/drivers/media/platform/samsung/exynos4-is/Kconfig
new file mode 100644
index 0000000000..7f9ba053dd
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/Kconfig
@@ -0,0 +1,84 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_SAMSUNG_EXYNOS4_IS
+ tristate "Samsung S5P/EXYNOS4 SoC series Camera Subsystem driver"
+ depends on V4L_PLATFORM_DRIVERS
+ depends on VIDEO_DEV && OF && COMMON_CLK
+ depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ help
+ Say Y here to enable camera host interface devices for
+ Samsung S5P and EXYNOS SoC series.
+
+if VIDEO_SAMSUNG_EXYNOS4_IS
+
+config VIDEO_EXYNOS4_IS_COMMON
+ tristate
+
+config VIDEO_S5P_FIMC
+ tristate "S5P/EXYNOS4 FIMC/CAMIF camera interface driver"
+ depends on I2C
+ depends on HAS_DMA
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ select MFD_SYSCON
+ select VIDEO_EXYNOS4_IS_COMMON
+ help
+ This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC camera host
+ interface and video postprocessor (FIMC) devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called s5p-fimc.
+
+config VIDEO_S5P_MIPI_CSIS
+ tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver"
+ depends on REGULATOR
+ select GENERIC_PHY
+ select V4L2_FWNODE
+ help
+ This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2
+ receiver (MIPI-CSIS) devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called s5p-csis.
+
+config VIDEO_EXYNOS_FIMC_LITE
+ tristate "EXYNOS FIMC-LITE camera interface driver"
+ depends on I2C
+ depends on SOC_EXYNOS4212 || SOC_EXYNOS4412 || SOC_EXYNOS5250 || COMPILE_TEST
+ depends on HAS_DMA
+ select VIDEOBUF2_DMA_CONTIG
+ select VIDEO_EXYNOS4_IS_COMMON
+ help
+ This is a V4L2 driver for Samsung EXYNOS4/5 SoC FIMC-LITE camera
+ host interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called exynos-fimc-lite.
+
+config VIDEO_EXYNOS4_FIMC_IS
+ tristate "EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver"
+ depends on I2C
+ depends on HAS_DMA
+ select VIDEOBUF2_DMA_CONTIG
+ depends on OF
+ select FW_LOADER
+ help
+ This is a V4L2 driver for Samsung EXYNOS4x12 SoC series
+ FIMC-IS (Imaging Subsystem).
+
+ To compile this driver as a module, choose M here: the
+ module will be called exynos4-fimc-is.
+
+config VIDEO_EXYNOS4_ISP_DMA_CAPTURE
+ bool "EXYNOS4x12 FIMC-IS ISP Direct DMA capture support"
+ depends on VIDEO_EXYNOS4_FIMC_IS
+ select VIDEO_EXYNOS4_IS_COMMON
+ default y
+ help
+ This option enables an additional video device node exposing a V4L2
+ video capture interface for the FIMC-IS ISP raw (Bayer) capture DMA.
+
+endif # VIDEO_SAMSUNG_EXYNOS4_IS
diff --git a/drivers/media/platform/samsung/exynos4-is/Makefile b/drivers/media/platform/samsung/exynos4-is/Makefile
new file mode 100644
index 0000000000..a5ab01c73b
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+s5p-fimc-objs := fimc-core.o fimc-reg.o fimc-m2m.o fimc-capture.o media-dev.o
+exynos-fimc-lite-objs += fimc-lite-reg.o fimc-lite.o
+s5p-csis-objs := mipi-csis.o
+exynos4-is-common-objs := common.o
+
+exynos-fimc-is-objs := fimc-is.o fimc-isp.o fimc-is-sensor.o fimc-is-regs.o
+exynos-fimc-is-objs += fimc-is-param.o fimc-is-errno.o fimc-is-i2c.o
+
+ifeq ($(CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE),y)
+exynos-fimc-is-objs += fimc-isp-video.o
+endif
+
+obj-$(CONFIG_VIDEO_S5P_MIPI_CSIS) += s5p-csis.o
+obj-$(CONFIG_VIDEO_EXYNOS_FIMC_LITE) += exynos-fimc-lite.o
+obj-$(CONFIG_VIDEO_EXYNOS4_FIMC_IS) += exynos-fimc-is.o
+obj-$(CONFIG_VIDEO_S5P_FIMC) += s5p-fimc.o
+obj-$(CONFIG_VIDEO_EXYNOS4_IS_COMMON) += exynos4-is-common.o
diff --git a/drivers/media/platform/samsung/exynos4-is/common.c b/drivers/media/platform/samsung/exynos4-is/common.c
new file mode 100644
index 0000000000..e41333535e
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/common.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung S5P/EXYNOS4 SoC Camera Subsystem driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <media/drv-intf/exynos-fimc.h>
+#include "common.h"
+
+/*
+ * Called with the media graph mutex held or media_entity_is_streaming(entity)
+ * true.
+ */
+struct v4l2_subdev *fimc_find_remote_sensor(struct media_entity *entity)
+{
+ struct media_pad *pad = &entity->pads[0];
+ struct v4l2_subdev *sd;
+
+ while (pad->flags & MEDIA_PAD_FL_SINK) {
+ /* source pad */
+ pad = media_pad_remote_pad_first(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+
+ if (sd->grp_id == GRP_ID_FIMC_IS_SENSOR ||
+ sd->grp_id == GRP_ID_SENSOR)
+ return sd;
+ /* sink pad */
+ pad = &sd->entity.pads[0];
+ }
+ return NULL;
+}
+EXPORT_SYMBOL(fimc_find_remote_sensor);
+
+void __fimc_vidioc_querycap(struct device *dev, struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, dev->driver->name, sizeof(cap->driver));
+ strscpy(cap->card, dev->driver->name, sizeof(cap->card));
+}
+EXPORT_SYMBOL(__fimc_vidioc_querycap);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/exynos4-is/common.h b/drivers/media/platform/samsung/exynos4-is/common.h
new file mode 100644
index 0000000000..0389b66e51
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/common.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/videodev2.h>
+#include <media/media-entity.h>
+#include <media/v4l2-subdev.h>
+
+struct v4l2_subdev *fimc_find_remote_sensor(struct media_entity *entity);
+void __fimc_vidioc_querycap(struct device *dev, struct v4l2_capability *cap);
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-capture.c b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c
new file mode 100644
index 0000000000..a0d43bf892
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c
@@ -0,0 +1,1897 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung S5P/EXYNOS4 SoC series camera interface (camera capture) driver
+ *
+ * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-rect.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "common.h"
+#include "fimc-core.h"
+#include "fimc-reg.h"
+#include "media-dev.h"
+
+static int fimc_capture_hw_init(struct fimc_dev *fimc)
+{
+ struct fimc_source_info *si = &fimc->vid_cap.source_config;
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ int ret;
+ unsigned long flags;
+
+ if (ctx == NULL || ctx->s_frame.fmt == NULL)
+ return -EINVAL;
+
+ if (si->fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) {
+ ret = fimc_hw_camblk_cfg_writeback(fimc);
+ if (ret < 0)
+ return ret;
+ }
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ fimc_prepare_dma_offset(ctx, &ctx->d_frame);
+ fimc_set_yuv_order(ctx);
+
+ fimc_hw_set_camera_polarity(fimc, si);
+ fimc_hw_set_camera_type(fimc, si);
+ fimc_hw_set_camera_source(fimc, si);
+ fimc_hw_set_camera_offset(fimc, &ctx->s_frame);
+
+ ret = fimc_set_scaler_info(ctx);
+ if (!ret) {
+ fimc_hw_set_input_path(ctx);
+ fimc_hw_set_prescaler(ctx);
+ fimc_hw_set_mainscaler(ctx);
+ fimc_hw_set_target_format(ctx);
+ fimc_hw_set_rotation(ctx);
+ fimc_hw_set_effect(ctx);
+ fimc_hw_set_output_path(ctx);
+ fimc_hw_set_out_dma(ctx);
+ if (fimc->drv_data->alpha_color)
+ fimc_hw_set_rgb_alpha(ctx);
+ clear_bit(ST_CAPT_APPLY_CFG, &fimc->state);
+ }
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return ret;
+}
+
+/*
+ * Reinitialize the driver so it is ready to start the streaming again.
+ * Set fimc->state to indicate stream off and the hardware shut down state.
+ * If not suspending (@suspend is false), return any buffers to videobuf2.
+ * Otherwise put any owned buffers onto the pending buffers queue, so they
+ * can be re-spun when the device is being resumed. Also perform FIMC
+ * software reset and disable streaming on the whole pipeline if required.
+ */
+static int fimc_capture_state_cleanup(struct fimc_dev *fimc, bool suspend)
+{
+ struct fimc_vid_cap *cap = &fimc->vid_cap;
+ struct fimc_vid_buffer *buf;
+ unsigned long flags;
+ bool streaming;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ streaming = fimc->state & (1 << ST_CAPT_ISP_STREAM);
+
+ fimc->state &= ~(1 << ST_CAPT_RUN | 1 << ST_CAPT_SHUT |
+ 1 << ST_CAPT_STREAM | 1 << ST_CAPT_ISP_STREAM);
+ if (suspend)
+ fimc->state |= (1 << ST_CAPT_SUSPENDED);
+ else
+ fimc->state &= ~(1 << ST_CAPT_PEND | 1 << ST_CAPT_SUSPENDED);
+
+ /* Release unused buffers */
+ while (!suspend && !list_empty(&cap->pending_buf_q)) {
+ buf = fimc_pending_queue_pop(cap);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ /* If suspending put unused buffers onto pending queue */
+ while (!list_empty(&cap->active_buf_q)) {
+ buf = fimc_active_queue_pop(cap);
+ if (suspend)
+ fimc_pending_queue_add(cap, buf);
+ else
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ fimc_hw_reset(fimc);
+ cap->buf_index = 0;
+
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (streaming)
+ return fimc_pipeline_call(&cap->ve, set_stream, 0);
+ else
+ return 0;
+}
+
+static int fimc_stop_capture(struct fimc_dev *fimc, bool suspend)
+{
+ unsigned long flags;
+
+ if (!fimc_capture_active(fimc))
+ return 0;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ set_bit(ST_CAPT_SHUT, &fimc->state);
+ fimc_deactivate_capture(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ wait_event_timeout(fimc->irq_queue,
+ !test_bit(ST_CAPT_SHUT, &fimc->state),
+ (2*HZ/10)); /* 200 ms */
+
+ return fimc_capture_state_cleanup(fimc, suspend);
+}
+
+/**
+ * fimc_capture_config_update - apply the camera interface configuration
+ * @ctx: FIMC capture context
+ *
+ * To be called from within the interrupt handler with fimc.slock
+ * spinlock held. It updates the camera pixel crop, rotation and
+ * image flip in H/W.
+ */
+static int fimc_capture_config_update(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ int ret;
+
+ fimc_hw_set_camera_offset(fimc, &ctx->s_frame);
+
+ ret = fimc_set_scaler_info(ctx);
+ if (ret)
+ return ret;
+
+ fimc_hw_set_prescaler(ctx);
+ fimc_hw_set_mainscaler(ctx);
+ fimc_hw_set_target_format(ctx);
+ fimc_hw_set_rotation(ctx);
+ fimc_hw_set_effect(ctx);
+ fimc_prepare_dma_offset(ctx, &ctx->d_frame);
+ fimc_hw_set_out_dma(ctx);
+ if (fimc->drv_data->alpha_color)
+ fimc_hw_set_rgb_alpha(ctx);
+
+ clear_bit(ST_CAPT_APPLY_CFG, &fimc->state);
+ return ret;
+}
+
+void fimc_capture_irq_handler(struct fimc_dev *fimc, int deq_buf)
+{
+ struct fimc_vid_cap *cap = &fimc->vid_cap;
+ struct fimc_pipeline *p = to_fimc_pipeline(cap->ve.pipe);
+ struct v4l2_subdev *csis = p->subdevs[IDX_CSIS];
+ struct fimc_frame *f = &cap->ctx->d_frame;
+ struct fimc_vid_buffer *v_buf;
+
+ if (test_and_clear_bit(ST_CAPT_SHUT, &fimc->state)) {
+ wake_up(&fimc->irq_queue);
+ goto done;
+ }
+
+ if (!list_empty(&cap->active_buf_q) &&
+ test_bit(ST_CAPT_RUN, &fimc->state) && deq_buf) {
+ v_buf = fimc_active_queue_pop(cap);
+
+ v_buf->vb.vb2_buf.timestamp = ktime_get_ns();
+ v_buf->vb.sequence = cap->frame_count++;
+
+ vb2_buffer_done(&v_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ if (!list_empty(&cap->pending_buf_q)) {
+
+ v_buf = fimc_pending_queue_pop(cap);
+ fimc_hw_set_output_addr(fimc, &v_buf->addr, cap->buf_index);
+ v_buf->index = cap->buf_index;
+
+ /* Move the buffer to the capture active queue */
+ fimc_active_queue_add(cap, v_buf);
+
+ dbg("next frame: %d, done frame: %d",
+ fimc_hw_get_frame_index(fimc), v_buf->index);
+
+ if (++cap->buf_index >= FIMC_MAX_OUT_BUFS)
+ cap->buf_index = 0;
+ }
+ /*
+ * Set up a buffer at MIPI-CSIS if current image format
+ * requires the frame embedded data capture.
+ */
+ if (f->fmt->mdataplanes && !list_empty(&cap->active_buf_q)) {
+ unsigned int plane = ffs(f->fmt->mdataplanes) - 1;
+ unsigned int size = f->payload[plane];
+ s32 index = fimc_hw_get_frame_index(fimc);
+ void *vaddr;
+
+ list_for_each_entry(v_buf, &cap->active_buf_q, list) {
+ if (v_buf->index != index)
+ continue;
+ vaddr = vb2_plane_vaddr(&v_buf->vb.vb2_buf, plane);
+ v4l2_subdev_call(csis, video, s_rx_buffer,
+ vaddr, &size);
+ break;
+ }
+ }
+
+ if (cap->active_buf_cnt == 0) {
+ if (deq_buf)
+ clear_bit(ST_CAPT_RUN, &fimc->state);
+
+ if (++cap->buf_index >= FIMC_MAX_OUT_BUFS)
+ cap->buf_index = 0;
+ } else {
+ set_bit(ST_CAPT_RUN, &fimc->state);
+ }
+
+ if (test_bit(ST_CAPT_APPLY_CFG, &fimc->state))
+ fimc_capture_config_update(cap->ctx);
+done:
+ if (cap->active_buf_cnt == 1) {
+ fimc_deactivate_capture(fimc);
+ clear_bit(ST_CAPT_STREAM, &fimc->state);
+ }
+
+ dbg("frame: %d, active_buf_cnt: %d",
+ fimc_hw_get_frame_index(fimc), cap->active_buf_cnt);
+}
+
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct fimc_ctx *ctx = q->drv_priv;
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
+ int min_bufs;
+ int ret;
+
+ vid_cap->frame_count = 0;
+
+ ret = fimc_capture_hw_init(fimc);
+ if (ret) {
+ fimc_capture_state_cleanup(fimc, false);
+ return ret;
+ }
+
+ set_bit(ST_CAPT_PEND, &fimc->state);
+
+ min_bufs = fimc->vid_cap.reqbufs_count > 1 ? 2 : 1;
+
+ if (vid_cap->active_buf_cnt >= min_bufs &&
+ !test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) {
+ fimc_activate_capture(ctx);
+
+ if (!test_and_set_bit(ST_CAPT_ISP_STREAM, &fimc->state))
+ return fimc_pipeline_call(&vid_cap->ve, set_stream, 1);
+ }
+
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct fimc_ctx *ctx = q->drv_priv;
+ struct fimc_dev *fimc = ctx->fimc_dev;
+
+ if (!fimc_capture_active(fimc))
+ return;
+
+ fimc_stop_capture(fimc, false);
+}
+
+int fimc_capture_suspend(struct fimc_dev *fimc)
+{
+ bool suspend = fimc_capture_busy(fimc);
+
+ int ret = fimc_stop_capture(fimc, suspend);
+ if (ret)
+ return ret;
+ return fimc_pipeline_call(&fimc->vid_cap.ve, close);
+}
+
+static void buffer_queue(struct vb2_buffer *vb);
+
+int fimc_capture_resume(struct fimc_dev *fimc)
+{
+ struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
+ struct exynos_video_entity *ve = &vid_cap->ve;
+ struct fimc_vid_buffer *buf;
+ int i;
+
+ if (!test_and_clear_bit(ST_CAPT_SUSPENDED, &fimc->state))
+ return 0;
+
+ INIT_LIST_HEAD(&fimc->vid_cap.active_buf_q);
+ vid_cap->buf_index = 0;
+ fimc_pipeline_call(ve, open, &ve->vdev.entity, false);
+ fimc_capture_hw_init(fimc);
+
+ clear_bit(ST_CAPT_SUSPENDED, &fimc->state);
+
+ for (i = 0; i < vid_cap->reqbufs_count; i++) {
+ if (list_empty(&vid_cap->pending_buf_q))
+ break;
+ buf = fimc_pending_queue_pop(vid_cap);
+ buffer_queue(&buf->vb.vb2_buf);
+ }
+ return 0;
+
+}
+
+static int queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct fimc_ctx *ctx = vq->drv_priv;
+ struct fimc_frame *frame = &ctx->d_frame;
+ struct fimc_fmt *fmt = frame->fmt;
+ unsigned long wh = frame->f_width * frame->f_height;
+ int i;
+
+ if (fmt == NULL)
+ return -EINVAL;
+
+ if (*num_planes) {
+ if (*num_planes != fmt->memplanes)
+ return -EINVAL;
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < (wh * fmt->depth[i]) / 8)
+ return -EINVAL;
+ return 0;
+ }
+
+ *num_planes = fmt->memplanes;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ unsigned int size = (wh * fmt->depth[i]) / 8;
+
+ if (fimc_fmt_is_user_defined(fmt->color))
+ sizes[i] = frame->payload[i];
+ else
+ sizes[i] = max_t(u32, size, frame->payload[i]);
+ }
+
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct fimc_ctx *ctx = vq->drv_priv;
+ int i;
+
+ if (ctx->d_frame.fmt == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < ctx->d_frame.fmt->memplanes; i++) {
+ unsigned long size = ctx->d_frame.payload[i];
+
+ if (vb2_plane_size(vb, i) < size) {
+ v4l2_err(&ctx->fimc_dev->vid_cap.ve.vdev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb, i), size);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb, i, size);
+ }
+
+ return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct fimc_vid_buffer *buf
+ = container_of(vbuf, struct fimc_vid_buffer, vb);
+ struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
+ struct exynos_video_entity *ve = &vid_cap->ve;
+ unsigned long flags;
+ int min_bufs;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ fimc_prepare_addr(ctx, &buf->vb.vb2_buf, &ctx->d_frame, &buf->addr);
+
+ if (!test_bit(ST_CAPT_SUSPENDED, &fimc->state) &&
+ !test_bit(ST_CAPT_STREAM, &fimc->state) &&
+ vid_cap->active_buf_cnt < FIMC_MAX_OUT_BUFS) {
+ /* Setup the buffer directly for processing. */
+ int buf_id = (vid_cap->reqbufs_count == 1) ? -1 :
+ vid_cap->buf_index;
+
+ fimc_hw_set_output_addr(fimc, &buf->addr, buf_id);
+ buf->index = vid_cap->buf_index;
+ fimc_active_queue_add(vid_cap, buf);
+
+ if (++vid_cap->buf_index >= FIMC_MAX_OUT_BUFS)
+ vid_cap->buf_index = 0;
+ } else {
+ fimc_pending_queue_add(vid_cap, buf);
+ }
+
+ min_bufs = vid_cap->reqbufs_count > 1 ? 2 : 1;
+
+
+ if (vb2_is_streaming(&vid_cap->vbq) &&
+ vid_cap->active_buf_cnt >= min_bufs &&
+ !test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) {
+ int ret;
+
+ fimc_activate_capture(ctx);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (test_and_set_bit(ST_CAPT_ISP_STREAM, &fimc->state))
+ return;
+
+ ret = fimc_pipeline_call(ve, set_stream, 1);
+ if (ret < 0)
+ v4l2_err(&ve->vdev, "stream on failed: %d\n", ret);
+ return;
+ }
+ spin_unlock_irqrestore(&fimc->slock, flags);
+}
+
+static const struct vb2_ops fimc_capture_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+static int fimc_capture_set_default_format(struct fimc_dev *fimc);
+
+static int fimc_capture_open(struct file *file)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct exynos_video_entity *ve = &vc->ve;
+ int ret = -EBUSY;
+
+ dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state);
+
+ mutex_lock(&fimc->lock);
+
+ if (fimc_m2m_active(fimc))
+ goto unlock;
+
+ set_bit(ST_CAPT_BUSY, &fimc->state);
+ ret = pm_runtime_resume_and_get(&fimc->pdev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_fh_open(file);
+ if (ret) {
+ pm_runtime_put_sync(&fimc->pdev->dev);
+ goto unlock;
+ }
+
+ if (v4l2_fh_is_singular_file(file)) {
+ fimc_md_graph_lock(ve);
+
+ ret = fimc_pipeline_call(ve, open, &ve->vdev.entity, true);
+
+ if (ret == 0)
+ ve->vdev.entity.use_count++;
+
+ fimc_md_graph_unlock(ve);
+
+ if (ret == 0)
+ ret = fimc_capture_set_default_format(fimc);
+
+ if (ret < 0) {
+ clear_bit(ST_CAPT_BUSY, &fimc->state);
+ pm_runtime_put_sync(&fimc->pdev->dev);
+ v4l2_fh_release(file);
+ }
+ }
+unlock:
+ mutex_unlock(&fimc->lock);
+ return ret;
+}
+
+static int fimc_capture_release(struct file *file)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ bool close = v4l2_fh_is_singular_file(file);
+ int ret;
+
+ dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state);
+
+ mutex_lock(&fimc->lock);
+
+ if (close && vc->streaming) {
+ video_device_pipeline_stop(&vc->ve.vdev);
+ vc->streaming = false;
+ }
+
+ ret = _vb2_fop_release(file, NULL);
+
+ if (close) {
+ clear_bit(ST_CAPT_BUSY, &fimc->state);
+ fimc_pipeline_call(&vc->ve, close);
+ clear_bit(ST_CAPT_SUSPENDED, &fimc->state);
+
+ fimc_md_graph_lock(&vc->ve);
+ vc->ve.vdev.entity.use_count--;
+ fimc_md_graph_unlock(&vc->ve);
+ }
+
+ pm_runtime_put_sync(&fimc->pdev->dev);
+ mutex_unlock(&fimc->lock);
+
+ return ret;
+}
+
+static const struct v4l2_file_operations fimc_capture_fops = {
+ .owner = THIS_MODULE,
+ .open = fimc_capture_open,
+ .release = fimc_capture_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * Format and crop negotiation helpers
+ */
+
+static struct fimc_fmt *fimc_capture_try_format(struct fimc_ctx *ctx,
+ u32 *width, u32 *height,
+ u32 *code, u32 *fourcc, int pad)
+{
+ bool rotation = ctx->rotation == 90 || ctx->rotation == 270;
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ const struct fimc_variant *var = fimc->variant;
+ const struct fimc_pix_limit *pl = var->pix_limit;
+ struct fimc_frame *dst = &ctx->d_frame;
+ u32 depth, min_w, max_w, min_h, align_h = 3;
+ u32 mask = FMT_FLAGS_CAM;
+ struct fimc_fmt *ffmt;
+
+ /* Conversion from/to JPEG or User Defined format is not supported */
+ if (code && ctx->s_frame.fmt && pad == FIMC_SD_PAD_SOURCE &&
+ fimc_fmt_is_user_defined(ctx->s_frame.fmt->color))
+ *code = ctx->s_frame.fmt->mbus_code;
+
+ if (fourcc && *fourcc != V4L2_PIX_FMT_JPEG && pad == FIMC_SD_PAD_SOURCE)
+ mask |= FMT_FLAGS_M2M;
+
+ if (pad == FIMC_SD_PAD_SINK_FIFO)
+ mask = FMT_FLAGS_WRITEBACK;
+
+ ffmt = fimc_find_format(fourcc, code, mask, 0);
+ if (WARN_ON(!ffmt))
+ return NULL;
+
+ if (code)
+ *code = ffmt->mbus_code;
+ if (fourcc)
+ *fourcc = ffmt->fourcc;
+
+ if (pad != FIMC_SD_PAD_SOURCE) {
+ max_w = fimc_fmt_is_user_defined(ffmt->color) ?
+ pl->scaler_dis_w : pl->scaler_en_w;
+ /* Apply the camera input interface pixel constraints */
+ v4l_bound_align_image(width, max_t(u32, *width, 32), max_w, 4,
+ height, max_t(u32, *height, 32),
+ FIMC_CAMIF_MAX_HEIGHT,
+ fimc_fmt_is_user_defined(ffmt->color) ?
+ 3 : 1,
+ 0);
+ return ffmt;
+ }
+ /* Can't scale or crop in transparent (JPEG) transfer mode */
+ if (fimc_fmt_is_user_defined(ffmt->color)) {
+ *width = ctx->s_frame.f_width;
+ *height = ctx->s_frame.f_height;
+ return ffmt;
+ }
+ /* Apply the scaler and the output DMA constraints */
+ max_w = rotation ? pl->out_rot_en_w : pl->out_rot_dis_w;
+ if (ctx->state & FIMC_COMPOSE) {
+ min_w = dst->offs_h + dst->width;
+ min_h = dst->offs_v + dst->height;
+ } else {
+ min_w = var->min_out_pixsize;
+ min_h = var->min_out_pixsize;
+ }
+ if (var->min_vsize_align == 1 && !rotation)
+ align_h = fimc_fmt_is_rgb(ffmt->color) ? 0 : 1;
+
+ depth = fimc_get_format_depth(ffmt);
+ v4l_bound_align_image(width, min_w, max_w,
+ ffs(var->min_out_pixsize) - 1,
+ height, min_h, FIMC_CAMIF_MAX_HEIGHT,
+ align_h,
+ 64/(ALIGN(depth, 8)));
+
+ dbg("pad%d: code: 0x%x, %dx%d. dst fmt: %dx%d",
+ pad, code ? *code : 0, *width, *height,
+ dst->f_width, dst->f_height);
+
+ return ffmt;
+}
+
+static void fimc_capture_try_selection(struct fimc_ctx *ctx,
+ struct v4l2_rect *r,
+ int target)
+{
+ bool rotate = ctx->rotation == 90 || ctx->rotation == 270;
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ const struct fimc_variant *var = fimc->variant;
+ const struct fimc_pix_limit *pl = var->pix_limit;
+ struct fimc_frame *sink = &ctx->s_frame;
+ u32 max_w, max_h, min_w = 0, min_h = 0, min_sz;
+ u32 align_sz = 0, align_h = 4;
+ u32 max_sc_h, max_sc_v;
+
+ /* In JPEG transparent transfer mode cropping is not supported */
+ if (fimc_fmt_is_user_defined(ctx->d_frame.fmt->color)) {
+ r->width = sink->f_width;
+ r->height = sink->f_height;
+ r->left = r->top = 0;
+ return;
+ }
+ if (target == V4L2_SEL_TGT_COMPOSE) {
+ u32 tmp_min_h = ffs(sink->width) - 3;
+ u32 tmp_min_v = ffs(sink->height) - 1;
+
+ if (ctx->rotation != 90 && ctx->rotation != 270)
+ align_h = 1;
+ max_sc_h = min(SCALER_MAX_HRATIO, 1 << tmp_min_h);
+ max_sc_v = min(SCALER_MAX_VRATIO, 1 << tmp_min_v);
+ min_sz = var->min_out_pixsize;
+ } else {
+ u32 depth = fimc_get_format_depth(sink->fmt);
+ align_sz = 64/ALIGN(depth, 8);
+ min_sz = var->min_inp_pixsize;
+ min_w = min_h = min_sz;
+ max_sc_h = max_sc_v = 1;
+ }
+ /*
+ * For the compose rectangle the following constraints must be met:
+ * - it must fit in the sink pad format rectangle (f_width/f_height);
+ * - maximum downscaling ratio is 64;
+ * - maximum crop size depends if the rotator is used or not;
+ * - the sink pad format width/height must be 4 multiple of the
+ * prescaler ratios determined by sink pad size and source pad crop,
+ * the prescaler ratio is returned by fimc_get_scaler_factor().
+ */
+ max_w = min_t(u32,
+ rotate ? pl->out_rot_en_w : pl->out_rot_dis_w,
+ rotate ? sink->f_height : sink->f_width);
+ max_h = min_t(u32, FIMC_CAMIF_MAX_HEIGHT, sink->f_height);
+
+ if (target == V4L2_SEL_TGT_COMPOSE) {
+ min_w = min_t(u32, max_w, sink->f_width / max_sc_h);
+ min_h = min_t(u32, max_h, sink->f_height / max_sc_v);
+ if (rotate) {
+ swap(max_sc_h, max_sc_v);
+ swap(min_w, min_h);
+ }
+ }
+ v4l_bound_align_image(&r->width, min_w, max_w, ffs(min_sz) - 1,
+ &r->height, min_h, max_h, align_h,
+ align_sz);
+ /* Adjust left/top if crop/compose rectangle is out of bounds */
+ r->left = clamp_t(u32, r->left, 0, sink->f_width - r->width);
+ r->top = clamp_t(u32, r->top, 0, sink->f_height - r->height);
+ r->left = round_down(r->left, var->hor_offs_align);
+
+ dbg("target %#x: (%d,%d)/%dx%d, sink fmt: %dx%d",
+ target, r->left, r->top, r->width, r->height,
+ sink->f_width, sink->f_height);
+}
+
+/*
+ * The video node ioctl operations
+ */
+static int fimc_cap_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+
+ __fimc_vidioc_querycap(&fimc->pdev->dev, cap);
+ return 0;
+}
+
+static int fimc_cap_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct fimc_fmt *fmt;
+
+ fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM | FMT_FLAGS_M2M,
+ f->index);
+ if (!fmt)
+ return -EINVAL;
+ f->pixelformat = fmt->fourcc;
+ return 0;
+}
+
+static struct media_entity *fimc_pipeline_get_head(struct media_entity *me)
+{
+ struct media_pad *pad = &me->pads[0];
+
+ while (!(pad->flags & MEDIA_PAD_FL_SOURCE)) {
+ pad = media_pad_remote_pad_first(pad);
+ if (!pad)
+ break;
+ me = pad->entity;
+ pad = &me->pads[0];
+ }
+
+ return me;
+}
+
+/**
+ * fimc_pipeline_try_format - negotiate and/or set formats at pipeline
+ * elements
+ * @ctx: FIMC capture context
+ * @tfmt: media bus format to try/set on subdevs
+ * @fmt_id: fimc pixel format id corresponding to returned @tfmt (output)
+ * @set: true to set format on subdevs, false to try only
+ */
+static int fimc_pipeline_try_format(struct fimc_ctx *ctx,
+ struct v4l2_mbus_framefmt *tfmt,
+ struct fimc_fmt **fmt_id,
+ bool set)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_pipeline *p = to_fimc_pipeline(fimc->vid_cap.ve.pipe);
+ struct v4l2_subdev *sd = p->subdevs[IDX_SENSOR];
+ struct v4l2_subdev_format sfmt = {
+ .which = set ? V4L2_SUBDEV_FORMAT_ACTIVE
+ : V4L2_SUBDEV_FORMAT_TRY,
+ };
+ struct v4l2_mbus_framefmt *mf = &sfmt.format;
+ struct media_entity *me;
+ struct fimc_fmt *ffmt;
+ struct media_pad *pad;
+ int ret, i = 1;
+ u32 fcc;
+
+ if (WARN_ON(!sd || !tfmt))
+ return -EINVAL;
+
+ sfmt.format = *tfmt;
+
+ me = fimc_pipeline_get_head(&sd->entity);
+
+ while (1) {
+ ffmt = fimc_find_format(NULL, mf->code != 0 ? &mf->code : NULL,
+ FMT_FLAGS_CAM, i++);
+ if (ffmt == NULL) {
+ /*
+ * Notify user-space if common pixel code for
+ * host and sensor does not exist.
+ */
+ return -EINVAL;
+ }
+ mf->code = tfmt->code = ffmt->mbus_code;
+
+ /* set format on all pipeline subdevs */
+ while (me != &fimc->vid_cap.subdev.entity) {
+ sd = media_entity_to_v4l2_subdev(me);
+
+ sfmt.pad = 0;
+ ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sfmt);
+ if (ret)
+ return ret;
+
+ if (me->pads[0].flags & MEDIA_PAD_FL_SINK) {
+ sfmt.pad = me->num_pads - 1;
+ mf->code = tfmt->code;
+ ret = v4l2_subdev_call(sd, pad, set_fmt, NULL,
+ &sfmt);
+ if (ret)
+ return ret;
+ }
+
+ pad = media_pad_remote_pad_first(&me->pads[sfmt.pad]);
+ if (!pad)
+ return -EINVAL;
+ me = pad->entity;
+ }
+
+ if (mf->code != tfmt->code)
+ continue;
+
+ fcc = ffmt->fourcc;
+ tfmt->width = mf->width;
+ tfmt->height = mf->height;
+ ffmt = fimc_capture_try_format(ctx, &tfmt->width, &tfmt->height,
+ NULL, &fcc, FIMC_SD_PAD_SINK_CAM);
+ ffmt = fimc_capture_try_format(ctx, &tfmt->width, &tfmt->height,
+ NULL, &fcc, FIMC_SD_PAD_SOURCE);
+ if (ffmt && ffmt->mbus_code)
+ mf->code = ffmt->mbus_code;
+ if (mf->width != tfmt->width || mf->height != tfmt->height)
+ continue;
+ tfmt->code = mf->code;
+ break;
+ }
+
+ if (fmt_id && ffmt)
+ *fmt_id = ffmt;
+ *tfmt = *mf;
+
+ return 0;
+}
+
+/**
+ * fimc_get_sensor_frame_desc - query the sensor for media bus frame parameters
+ * @sensor: pointer to the sensor subdev
+ * @plane_fmt: provides plane sizes corresponding to the frame layout entries
+ * @num_planes: number of planes
+ * @try: true to set the frame parameters, false to query only
+ *
+ * This function is used by this driver only for compressed/blob data formats.
+ */
+static int fimc_get_sensor_frame_desc(struct v4l2_subdev *sensor,
+ struct v4l2_plane_pix_format *plane_fmt,
+ unsigned int num_planes, bool try)
+{
+ struct v4l2_mbus_frame_desc fd = { };
+ int i, ret;
+ int pad;
+
+ for (i = 0; i < num_planes; i++)
+ fd.entry[i].length = plane_fmt[i].sizeimage;
+
+ pad = sensor->entity.num_pads - 1;
+ if (try)
+ ret = v4l2_subdev_call(sensor, pad, set_frame_desc, pad, &fd);
+ else
+ ret = v4l2_subdev_call(sensor, pad, get_frame_desc, pad, &fd);
+
+ if (ret < 0)
+ return ret;
+
+ if (num_planes != fd.num_entries)
+ return -EINVAL;
+
+ for (i = 0; i < num_planes; i++)
+ plane_fmt[i].sizeimage = fd.entry[i].length;
+
+ if (fd.entry[0].length > FIMC_MAX_JPEG_BUF_SIZE) {
+ v4l2_err(sensor->v4l2_dev, "Unsupported buffer size: %u\n",
+ fd.entry[0].length);
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fimc_cap_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+
+ __fimc_get_format(&fimc->vid_cap.ctx->d_frame, f);
+ return 0;
+}
+
+/*
+ * Try or set format on the fimc.X.capture video node and additionally
+ * on the whole pipeline if @try is false.
+ * Locking: the caller must _not_ hold the graph mutex.
+ */
+static int __video_try_or_set_format(struct fimc_dev *fimc,
+ struct v4l2_format *f, bool try,
+ struct fimc_fmt **inp_fmt,
+ struct fimc_fmt **out_fmt)
+{
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct exynos_video_entity *ve = &vc->ve;
+ struct fimc_ctx *ctx = vc->ctx;
+ unsigned int width = 0, height = 0;
+ int ret = 0;
+
+ /* Pre-configure format at the camera input interface, for JPEG only */
+ if (fimc_jpeg_fourcc(pix->pixelformat)) {
+ fimc_capture_try_format(ctx, &pix->width, &pix->height,
+ NULL, &pix->pixelformat,
+ FIMC_SD_PAD_SINK_CAM);
+ if (try) {
+ width = pix->width;
+ height = pix->height;
+ } else {
+ ctx->s_frame.f_width = pix->width;
+ ctx->s_frame.f_height = pix->height;
+ }
+ }
+
+ /* Try the format at the scaler and the DMA output */
+ *out_fmt = fimc_capture_try_format(ctx, &pix->width, &pix->height,
+ NULL, &pix->pixelformat,
+ FIMC_SD_PAD_SOURCE);
+ if (*out_fmt == NULL)
+ return -EINVAL;
+
+ /* Restore image width/height for JPEG (no resizing supported). */
+ if (try && fimc_jpeg_fourcc(pix->pixelformat)) {
+ pix->width = width;
+ pix->height = height;
+ }
+
+ /* Try to match format at the host and the sensor */
+ if (!vc->user_subdev_api) {
+ struct v4l2_mbus_framefmt mbus_fmt;
+ struct v4l2_mbus_framefmt *mf;
+
+ mf = try ? &mbus_fmt : &fimc->vid_cap.ci_fmt;
+
+ mf->code = (*out_fmt)->mbus_code;
+ mf->width = pix->width;
+ mf->height = pix->height;
+
+ fimc_md_graph_lock(ve);
+ ret = fimc_pipeline_try_format(ctx, mf, inp_fmt, try);
+ fimc_md_graph_unlock(ve);
+
+ if (ret < 0)
+ return ret;
+
+ pix->width = mf->width;
+ pix->height = mf->height;
+ }
+
+ fimc_adjust_mplane_format(*out_fmt, pix->width, pix->height, pix);
+
+ if ((*out_fmt)->flags & FMT_FLAGS_COMPRESSED) {
+ struct v4l2_subdev *sensor;
+
+ fimc_md_graph_lock(ve);
+
+ sensor = __fimc_md_get_subdev(ve->pipe, IDX_SENSOR);
+ if (sensor)
+ fimc_get_sensor_frame_desc(sensor, pix->plane_fmt,
+ (*out_fmt)->memplanes, try);
+ else
+ ret = -EPIPE;
+
+ fimc_md_graph_unlock(ve);
+ }
+
+ return ret;
+}
+
+static int fimc_cap_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_fmt *out_fmt = NULL, *inp_fmt = NULL;
+
+ return __video_try_or_set_format(fimc, f, true, &inp_fmt, &out_fmt);
+}
+
+static void fimc_capture_mark_jpeg_xfer(struct fimc_ctx *ctx,
+ enum fimc_color_fmt color)
+{
+ bool jpeg = fimc_fmt_is_user_defined(color);
+
+ ctx->scaler.enabled = !jpeg;
+ fimc_ctrls_activate(ctx, !jpeg);
+
+ if (jpeg)
+ set_bit(ST_CAPT_JPEG, &ctx->fimc_dev->state);
+ else
+ clear_bit(ST_CAPT_JPEG, &ctx->fimc_dev->state);
+}
+
+static int __fimc_capture_set_format(struct fimc_dev *fimc,
+ struct v4l2_format *f)
+{
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct fimc_ctx *ctx = vc->ctx;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct fimc_frame *ff = &ctx->d_frame;
+ struct fimc_fmt *inp_fmt = NULL;
+ int ret, i;
+
+ if (vb2_is_busy(&fimc->vid_cap.vbq))
+ return -EBUSY;
+
+ ret = __video_try_or_set_format(fimc, f, false, &inp_fmt, &ff->fmt);
+ if (ret < 0)
+ return ret;
+
+ /* Update RGB Alpha control state and value range */
+ fimc_alpha_ctrl_update(ctx);
+
+ for (i = 0; i < ff->fmt->memplanes; i++) {
+ ff->bytesperline[i] = pix->plane_fmt[i].bytesperline;
+ ff->payload[i] = pix->plane_fmt[i].sizeimage;
+ }
+
+ set_frame_bounds(ff, pix->width, pix->height);
+ /* Reset the composition rectangle if not yet configured */
+ if (!(ctx->state & FIMC_COMPOSE))
+ set_frame_crop(ff, 0, 0, pix->width, pix->height);
+
+ fimc_capture_mark_jpeg_xfer(ctx, ff->fmt->color);
+
+ /* Reset cropping and set format at the camera interface input */
+ if (!vc->user_subdev_api) {
+ ctx->s_frame.fmt = inp_fmt;
+ set_frame_bounds(&ctx->s_frame, pix->width, pix->height);
+ set_frame_crop(&ctx->s_frame, 0, 0, pix->width, pix->height);
+ }
+
+ return ret;
+}
+
+static int fimc_cap_s_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+
+ return __fimc_capture_set_format(fimc, f);
+}
+
+static int fimc_cap_enum_input(struct file *file, void *priv,
+ struct v4l2_input *i)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct exynos_video_entity *ve = &fimc->vid_cap.ve;
+ struct v4l2_subdev *sd;
+
+ if (i->index != 0)
+ return -EINVAL;
+
+ i->type = V4L2_INPUT_TYPE_CAMERA;
+ fimc_md_graph_lock(ve);
+ sd = __fimc_md_get_subdev(ve->pipe, IDX_SENSOR);
+ fimc_md_graph_unlock(ve);
+
+ if (sd)
+ strscpy(i->name, sd->name, sizeof(i->name));
+
+ return 0;
+}
+
+static int fimc_cap_s_input(struct file *file, void *priv, unsigned int i)
+{
+ return i == 0 ? i : -EINVAL;
+}
+
+static int fimc_cap_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+/**
+ * fimc_pipeline_validate - check for formats inconsistencies
+ * between source and sink pad of each link
+ * @fimc: the FIMC device this context applies to
+ *
+ * Return 0 if all formats match or -EPIPE otherwise.
+ */
+static int fimc_pipeline_validate(struct fimc_dev *fimc)
+{
+ struct v4l2_subdev_format sink_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_subdev_format src_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct v4l2_subdev *sd = &vc->subdev;
+ struct fimc_pipeline *p = to_fimc_pipeline(vc->ve.pipe);
+ struct media_pad *sink_pad, *src_pad;
+ int i, ret;
+
+ while (1) {
+ /*
+ * Find current entity sink pad and any remote sink pad linked
+ * to it. We stop if there is no sink pad in current entity or
+ * it is not linked to any other remote entity.
+ */
+ src_pad = NULL;
+
+ for (i = 0; i < sd->entity.num_pads; i++) {
+ struct media_pad *p = &sd->entity.pads[i];
+
+ if (p->flags & MEDIA_PAD_FL_SINK) {
+ sink_pad = p;
+ src_pad = media_pad_remote_pad_first(sink_pad);
+ if (src_pad)
+ break;
+ }
+ }
+
+ if (!src_pad || !is_media_entity_v4l2_subdev(src_pad->entity))
+ break;
+
+ /* Don't call FIMC subdev operation to avoid nested locking */
+ if (sd == &vc->subdev) {
+ struct fimc_frame *ff = &vc->ctx->s_frame;
+ sink_fmt.format.width = ff->f_width;
+ sink_fmt.format.height = ff->f_height;
+ sink_fmt.format.code = ff->fmt ? ff->fmt->mbus_code : 0;
+ } else {
+ sink_fmt.pad = sink_pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sink_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+ }
+
+ /* Retrieve format at the source pad */
+ sd = media_entity_to_v4l2_subdev(src_pad->entity);
+ src_fmt.pad = src_pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ if (src_fmt.format.width != sink_fmt.format.width ||
+ src_fmt.format.height != sink_fmt.format.height ||
+ src_fmt.format.code != sink_fmt.format.code)
+ return -EPIPE;
+
+ if (sd == p->subdevs[IDX_SENSOR] &&
+ fimc_user_defined_mbus_fmt(src_fmt.format.code)) {
+ struct v4l2_plane_pix_format plane_fmt[FIMC_MAX_PLANES];
+ struct fimc_frame *frame = &vc->ctx->d_frame;
+ unsigned int i;
+
+ ret = fimc_get_sensor_frame_desc(sd, plane_fmt,
+ frame->fmt->memplanes,
+ false);
+ if (ret < 0)
+ return -EPIPE;
+
+ for (i = 0; i < frame->fmt->memplanes; i++)
+ if (frame->payload[i] < plane_fmt[i].sizeimage)
+ return -EPIPE;
+ }
+ }
+ return 0;
+}
+
+static int fimc_cap_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct fimc_source_info *si = NULL;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ if (fimc_capture_active(fimc))
+ return -EBUSY;
+
+ ret = video_device_pipeline_start(&vc->ve.vdev, &vc->ve.pipe->mp);
+ if (ret < 0)
+ return ret;
+
+ sd = __fimc_md_get_subdev(vc->ve.pipe, IDX_SENSOR);
+ if (sd)
+ si = v4l2_get_subdev_hostdata(sd);
+
+ if (si == NULL) {
+ ret = -EPIPE;
+ goto err_p_stop;
+ }
+ /*
+ * Save configuration data related to currently attached image
+ * sensor or other data source, e.g. FIMC-IS.
+ */
+ vc->source_config = *si;
+
+ if (vc->input == GRP_ID_FIMC_IS)
+ vc->source_config.fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK;
+
+ if (vc->user_subdev_api) {
+ ret = fimc_pipeline_validate(fimc);
+ if (ret < 0)
+ goto err_p_stop;
+ }
+
+ ret = vb2_ioctl_streamon(file, priv, type);
+ if (!ret) {
+ vc->streaming = true;
+ return ret;
+ }
+
+err_p_stop:
+ video_device_pipeline_stop(&vc->ve.vdev);
+ return ret;
+}
+
+static int fimc_cap_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ int ret;
+
+ ret = vb2_ioctl_streamoff(file, priv, type);
+ if (ret < 0)
+ return ret;
+
+ if (vc->streaming) {
+ video_device_pipeline_stop(&vc->ve.vdev);
+ vc->streaming = false;
+ }
+
+ return 0;
+}
+
+static int fimc_cap_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ int ret;
+
+ ret = vb2_ioctl_reqbufs(file, priv, reqbufs);
+
+ if (!ret)
+ fimc->vid_cap.reqbufs_count = reqbufs->count;
+
+ return ret;
+}
+
+static int fimc_cap_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ struct fimc_frame *f = &ctx->s_frame;
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &ctx->d_frame;
+ fallthrough;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = f->o_width;
+ s->r.height = f->o_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ f = &ctx->d_frame;
+ fallthrough;
+ case V4L2_SEL_TGT_CROP:
+ s->r.left = f->offs_h;
+ s->r.top = f->offs_v;
+ s->r.width = f->width;
+ s->r.height = f->height;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int fimc_cap_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ struct v4l2_rect rect = s->r;
+ struct fimc_frame *f;
+ unsigned long flags;
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE)
+ f = &ctx->d_frame;
+ else if (s->target == V4L2_SEL_TGT_CROP)
+ f = &ctx->s_frame;
+ else
+ return -EINVAL;
+
+ fimc_capture_try_selection(ctx, &rect, s->target);
+
+ if (s->flags & V4L2_SEL_FLAG_LE &&
+ !v4l2_rect_enclosed(&rect, &s->r))
+ return -ERANGE;
+
+ if (s->flags & V4L2_SEL_FLAG_GE &&
+ !v4l2_rect_enclosed(&s->r, &rect))
+ return -ERANGE;
+
+ s->r = rect;
+ spin_lock_irqsave(&fimc->slock, flags);
+ set_frame_crop(f, s->r.left, s->r.top, s->r.width,
+ s->r.height);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ set_bit(ST_CAPT_APPLY_CFG, &fimc->state);
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = {
+ .vidioc_querycap = fimc_cap_querycap,
+
+ .vidioc_enum_fmt_vid_cap = fimc_cap_enum_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = fimc_cap_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = fimc_cap_s_fmt_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = fimc_cap_g_fmt_mplane,
+
+ .vidioc_reqbufs = fimc_cap_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+
+ .vidioc_streamon = fimc_cap_streamon,
+ .vidioc_streamoff = fimc_cap_streamoff,
+
+ .vidioc_g_selection = fimc_cap_g_selection,
+ .vidioc_s_selection = fimc_cap_s_selection,
+
+ .vidioc_enum_input = fimc_cap_enum_input,
+ .vidioc_s_input = fimc_cap_s_input,
+ .vidioc_g_input = fimc_cap_g_input,
+};
+
+/* Capture subdev media entity operations */
+static int fimc_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct v4l2_subdev *sensor;
+
+ if (!is_media_entity_v4l2_subdev(remote->entity))
+ return -EINVAL;
+
+ if (WARN_ON(fimc == NULL))
+ return 0;
+
+ dbg("%s --> %s, flags: 0x%x. input: 0x%x",
+ local->entity->name, remote->entity->name, flags,
+ fimc->vid_cap.input);
+
+ if (!(flags & MEDIA_LNK_FL_ENABLED)) {
+ fimc->vid_cap.input = 0;
+ return 0;
+ }
+
+ if (vc->input != 0)
+ return -EBUSY;
+
+ vc->input = sd->grp_id;
+
+ if (vc->user_subdev_api)
+ return 0;
+
+ /* Inherit V4L2 controls from the image sensor subdev. */
+ sensor = fimc_find_remote_sensor(&vc->subdev.entity);
+ if (sensor == NULL)
+ return 0;
+
+ return v4l2_ctrl_add_handler(&vc->ctx->ctrls.handler,
+ sensor->ctrl_handler, NULL, true);
+}
+
+static const struct media_entity_operations fimc_sd_media_ops = {
+ .link_setup = fimc_link_setup,
+};
+
+/**
+ * fimc_sensor_notify - v4l2_device notification from a sensor subdev
+ * @sd: pointer to a subdev generating the notification
+ * @notification: the notification type, must be S5P_FIMC_TX_END_NOTIFY
+ * @arg: pointer to an u32 type integer that stores the frame payload value
+ *
+ * The End Of Frame notification sent by sensor subdev in its still capture
+ * mode. If there is only a single VSYNC generated by the sensor at the
+ * beginning of a frame transmission, FIMC does not issue the LastIrq
+ * (end of frame) interrupt. And this notification is used to complete the
+ * frame capture and returning a buffer to user-space. Subdev drivers should
+ * call this notification from their last 'End of frame capture' interrupt.
+ */
+void fimc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification,
+ void *arg)
+{
+ struct fimc_source_info *si;
+ struct fimc_vid_buffer *buf;
+ struct fimc_md *fmd;
+ struct fimc_dev *fimc;
+ unsigned long flags;
+
+ if (sd == NULL)
+ return;
+
+ si = v4l2_get_subdev_hostdata(sd);
+ fmd = entity_to_fimc_mdev(&sd->entity);
+
+ spin_lock_irqsave(&fmd->slock, flags);
+
+ fimc = si ? source_to_sensor_info(si)->host : NULL;
+
+ if (fimc && arg && notification == S5P_FIMC_TX_END_NOTIFY &&
+ test_bit(ST_CAPT_PEND, &fimc->state)) {
+ unsigned long irq_flags;
+ spin_lock_irqsave(&fimc->slock, irq_flags);
+ if (!list_empty(&fimc->vid_cap.active_buf_q)) {
+ buf = list_entry(fimc->vid_cap.active_buf_q.next,
+ struct fimc_vid_buffer, list);
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0,
+ *((u32 *)arg));
+ }
+ fimc_capture_irq_handler(fimc, 1);
+ fimc_deactivate_capture(fimc);
+ spin_unlock_irqrestore(&fimc->slock, irq_flags);
+ }
+ spin_unlock_irqrestore(&fmd->slock, flags);
+}
+
+static int fimc_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct fimc_fmt *fmt;
+
+ fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, code->index);
+ if (!fmt)
+ return -EINVAL;
+ code->code = fmt->mbus_code;
+ return 0;
+}
+
+static int fimc_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ struct fimc_frame *ff = &ctx->s_frame;
+ struct v4l2_mbus_framefmt *mf;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ fmt->format = *mf;
+ return 0;
+ }
+
+ mf = &fmt->format;
+ mutex_lock(&fimc->lock);
+
+ switch (fmt->pad) {
+ case FIMC_SD_PAD_SOURCE:
+ if (!WARN_ON(ff->fmt == NULL))
+ mf->code = ff->fmt->mbus_code;
+ /* Sink pads crop rectangle size */
+ mf->width = ff->width;
+ mf->height = ff->height;
+ break;
+ case FIMC_SD_PAD_SINK_FIFO:
+ *mf = fimc->vid_cap.wb_fmt;
+ break;
+ case FIMC_SD_PAD_SINK_CAM:
+ default:
+ *mf = fimc->vid_cap.ci_fmt;
+ break;
+ }
+
+ mutex_unlock(&fimc->lock);
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+
+ return 0;
+}
+
+static int fimc_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct fimc_ctx *ctx = vc->ctx;
+ struct fimc_frame *ff;
+ struct fimc_fmt *ffmt;
+
+ dbg("pad%d: code: 0x%x, %dx%d",
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ if (fmt->pad == FIMC_SD_PAD_SOURCE && vb2_is_busy(&vc->vbq))
+ return -EBUSY;
+
+ mutex_lock(&fimc->lock);
+ ffmt = fimc_capture_try_format(ctx, &mf->width, &mf->height,
+ &mf->code, NULL, fmt->pad);
+ mutex_unlock(&fimc->lock);
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ *mf = fmt->format;
+ return 0;
+ }
+ /* There must be a bug in the driver if this happens */
+ if (WARN_ON(ffmt == NULL))
+ return -EINVAL;
+
+ /* Update RGB Alpha control state and value range */
+ fimc_alpha_ctrl_update(ctx);
+
+ fimc_capture_mark_jpeg_xfer(ctx, ffmt->color);
+ if (fmt->pad == FIMC_SD_PAD_SOURCE) {
+ ff = &ctx->d_frame;
+ /* Sink pads crop rectangle size */
+ mf->width = ctx->s_frame.width;
+ mf->height = ctx->s_frame.height;
+ } else {
+ ff = &ctx->s_frame;
+ }
+
+ mutex_lock(&fimc->lock);
+ set_frame_bounds(ff, mf->width, mf->height);
+
+ if (fmt->pad == FIMC_SD_PAD_SINK_FIFO)
+ vc->wb_fmt = *mf;
+ else if (fmt->pad == FIMC_SD_PAD_SINK_CAM)
+ vc->ci_fmt = *mf;
+
+ ff->fmt = ffmt;
+
+ /* Reset the crop rectangle if required. */
+ if (!(fmt->pad == FIMC_SD_PAD_SOURCE && (ctx->state & FIMC_COMPOSE)))
+ set_frame_crop(ff, 0, 0, mf->width, mf->height);
+
+ if (fmt->pad != FIMC_SD_PAD_SOURCE)
+ ctx->state &= ~FIMC_COMPOSE;
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static int fimc_subdev_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ struct fimc_frame *f = &ctx->s_frame;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+
+ if (sel->pad == FIMC_SD_PAD_SOURCE)
+ return -EINVAL;
+
+ mutex_lock(&fimc->lock);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &ctx->d_frame;
+ fallthrough;
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ r->width = f->o_width;
+ r->height = f->o_height;
+ r->left = 0;
+ r->top = 0;
+ mutex_unlock(&fimc->lock);
+ return 0;
+
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad);
+ f = &ctx->d_frame;
+ break;
+ default:
+ mutex_unlock(&fimc->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sel->r = *try_sel;
+ } else {
+ r->left = f->offs_h;
+ r->top = f->offs_v;
+ r->width = f->width;
+ r->height = f->height;
+ }
+
+ dbg("target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d",
+ sel->pad, r->left, r->top, r->width, r->height,
+ f->f_width, f->f_height);
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static int fimc_subdev_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct fimc_ctx *ctx = fimc->vid_cap.ctx;
+ struct fimc_frame *f = &ctx->s_frame;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+ unsigned long flags;
+
+ if (sel->pad == FIMC_SD_PAD_SOURCE)
+ return -EINVAL;
+
+ mutex_lock(&fimc->lock);
+ fimc_capture_try_selection(ctx, r, V4L2_SEL_TGT_CROP);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad);
+ f = &ctx->d_frame;
+ break;
+ default:
+ mutex_unlock(&fimc->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *try_sel = sel->r;
+ } else {
+ spin_lock_irqsave(&fimc->slock, flags);
+ set_frame_crop(f, r->left, r->top, r->width, r->height);
+ set_bit(ST_CAPT_APPLY_CFG, &fimc->state);
+ if (sel->target == V4L2_SEL_TGT_COMPOSE)
+ ctx->state |= FIMC_COMPOSE;
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ }
+
+ dbg("target %#x: (%d,%d)/%dx%d", sel->target, r->left, r->top,
+ r->width, r->height);
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops fimc_subdev_pad_ops = {
+ .enum_mbus_code = fimc_subdev_enum_mbus_code,
+ .get_selection = fimc_subdev_get_selection,
+ .set_selection = fimc_subdev_set_selection,
+ .get_fmt = fimc_subdev_get_fmt,
+ .set_fmt = fimc_subdev_set_fmt,
+};
+
+static const struct v4l2_subdev_ops fimc_subdev_ops = {
+ .pad = &fimc_subdev_pad_ops,
+};
+
+/* Set default format at the sensor and host interface */
+static int fimc_capture_set_default_format(struct fimc_dev *fimc)
+{
+ struct v4l2_format fmt = {
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+ .fmt.pix_mp = {
+ .width = FIMC_DEFAULT_WIDTH,
+ .height = FIMC_DEFAULT_HEIGHT,
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ },
+ };
+
+ return __fimc_capture_set_format(fimc, &fmt);
+}
+
+/* fimc->lock must be already initialized */
+static int fimc_register_capture_device(struct fimc_dev *fimc,
+ struct v4l2_device *v4l2_dev)
+{
+ struct video_device *vfd = &fimc->vid_cap.ve.vdev;
+ struct vb2_queue *q = &fimc->vid_cap.vbq;
+ struct fimc_ctx *ctx;
+ struct fimc_vid_cap *vid_cap;
+ struct fimc_fmt *fmt;
+ int ret = -ENOMEM;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->fimc_dev = fimc;
+ ctx->in_path = FIMC_IO_CAMERA;
+ ctx->out_path = FIMC_IO_DMA;
+ ctx->state = FIMC_CTX_CAP;
+ ctx->s_frame.fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, 0);
+ ctx->d_frame.fmt = ctx->s_frame.fmt;
+
+ memset(vfd, 0, sizeof(*vfd));
+ snprintf(vfd->name, sizeof(vfd->name), "fimc.%d.capture", fimc->id);
+
+ vfd->fops = &fimc_capture_fops;
+ vfd->ioctl_ops = &fimc_capture_ioctl_ops;
+ vfd->v4l2_dev = v4l2_dev;
+ vfd->minor = -1;
+ vfd->release = video_device_release_empty;
+ vfd->queue = q;
+ vfd->lock = &fimc->lock;
+ vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+
+ video_set_drvdata(vfd, fimc);
+ vid_cap = &fimc->vid_cap;
+ vid_cap->active_buf_cnt = 0;
+ vid_cap->reqbufs_count = 0;
+ vid_cap->ctx = ctx;
+
+ INIT_LIST_HEAD(&vid_cap->pending_buf_q);
+ INIT_LIST_HEAD(&vid_cap->active_buf_q);
+
+ memset(q, 0, sizeof(*q));
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ q->drv_priv = ctx;
+ q->ops = &fimc_capture_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct fimc_vid_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &fimc->lock;
+ q->dev = &fimc->pdev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_free_ctx;
+
+ /* Default format configuration */
+ fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, 0);
+ vid_cap->ci_fmt.width = FIMC_DEFAULT_WIDTH;
+ vid_cap->ci_fmt.height = FIMC_DEFAULT_HEIGHT;
+ vid_cap->ci_fmt.code = fmt->mbus_code;
+
+ ctx->s_frame.width = FIMC_DEFAULT_WIDTH;
+ ctx->s_frame.height = FIMC_DEFAULT_HEIGHT;
+ ctx->s_frame.fmt = fmt;
+
+ fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_WRITEBACK, 0);
+ vid_cap->wb_fmt = vid_cap->ci_fmt;
+ vid_cap->wb_fmt.code = fmt->mbus_code;
+
+ vid_cap->vd_pad.flags = MEDIA_PAD_FL_SINK;
+ vfd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ ret = media_entity_pads_init(&vfd->entity, 1, &vid_cap->vd_pad);
+ if (ret)
+ goto err_free_ctx;
+
+ ret = fimc_ctrls_create(ctx);
+ if (ret)
+ goto err_me_cleanup;
+
+ ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
+ if (ret)
+ goto err_ctrl_free;
+
+ v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
+ vfd->name, video_device_node_name(vfd));
+
+ vfd->ctrl_handler = &ctx->ctrls.handler;
+ return 0;
+
+err_ctrl_free:
+ fimc_ctrls_delete(ctx);
+err_me_cleanup:
+ media_entity_cleanup(&vfd->entity);
+err_free_ctx:
+ kfree(ctx);
+ return ret;
+}
+
+static int fimc_capture_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ int ret;
+
+ if (fimc == NULL)
+ return -ENXIO;
+
+ ret = fimc_register_m2m_device(fimc, sd->v4l2_dev);
+ if (ret)
+ return ret;
+
+ fimc->vid_cap.ve.pipe = v4l2_get_subdev_hostdata(sd);
+
+ ret = fimc_register_capture_device(fimc, sd->v4l2_dev);
+ if (ret) {
+ fimc_unregister_m2m_device(fimc);
+ fimc->vid_cap.ve.pipe = NULL;
+ }
+
+ return ret;
+}
+
+static void fimc_capture_subdev_unregistered(struct v4l2_subdev *sd)
+{
+ struct fimc_dev *fimc = v4l2_get_subdevdata(sd);
+ struct video_device *vdev;
+
+ if (fimc == NULL)
+ return;
+
+ mutex_lock(&fimc->lock);
+
+ fimc_unregister_m2m_device(fimc);
+ vdev = &fimc->vid_cap.ve.vdev;
+
+ if (video_is_registered(vdev)) {
+ video_unregister_device(vdev);
+ media_entity_cleanup(&vdev->entity);
+ fimc_ctrls_delete(fimc->vid_cap.ctx);
+ fimc->vid_cap.ve.pipe = NULL;
+ }
+ kfree(fimc->vid_cap.ctx);
+ fimc->vid_cap.ctx = NULL;
+
+ mutex_unlock(&fimc->lock);
+}
+
+static const struct v4l2_subdev_internal_ops fimc_capture_sd_internal_ops = {
+ .registered = fimc_capture_subdev_registered,
+ .unregistered = fimc_capture_subdev_unregistered,
+};
+
+int fimc_initialize_capture_subdev(struct fimc_dev *fimc)
+{
+ struct v4l2_subdev *sd = &fimc->vid_cap.subdev;
+ int ret;
+
+ v4l2_subdev_init(sd, &fimc_subdev_ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ snprintf(sd->name, sizeof(sd->name), "FIMC.%d", fimc->id);
+
+ fimc->vid_cap.sd_pads[FIMC_SD_PAD_SINK_CAM].flags = MEDIA_PAD_FL_SINK;
+ fimc->vid_cap.sd_pads[FIMC_SD_PAD_SINK_FIFO].flags = MEDIA_PAD_FL_SINK;
+ fimc->vid_cap.sd_pads[FIMC_SD_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&sd->entity, FIMC_SD_PADS_NUM,
+ fimc->vid_cap.sd_pads);
+ if (ret)
+ return ret;
+
+ sd->entity.ops = &fimc_sd_media_ops;
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ sd->internal_ops = &fimc_capture_sd_internal_ops;
+ v4l2_set_subdevdata(sd, fimc);
+ return 0;
+}
+
+void fimc_unregister_capture_subdev(struct fimc_dev *fimc)
+{
+ struct v4l2_subdev *sd = &fimc->vid_cap.subdev;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_set_subdevdata(sd, NULL);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.c b/drivers/media/platform/samsung/exynos4-is/fimc-core.c
new file mode 100644
index 0000000000..97908778e1
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.c
@@ -0,0 +1,1175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S5P/EXYNOS4 SoC series FIMC (CAMIF) driver
+ *
+ * Copyright (C) 2010-2012 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "fimc-core.h"
+#include "fimc-reg.h"
+#include "media-dev.h"
+
+static char *fimc_clocks[MAX_FIMC_CLOCKS] = {
+ "sclk_fimc", "fimc"
+};
+
+static struct fimc_fmt fimc_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = { 16 },
+ .color = FIMC_FMT_RGB565,
+ .memplanes = 1,
+ .colplanes = 1,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR666,
+ .depth = { 32 },
+ .color = FIMC_FMT_RGB666,
+ .memplanes = 1,
+ .colplanes = 1,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR32,
+ .depth = { 32 },
+ .color = FIMC_FMT_RGB888,
+ .memplanes = 1,
+ .colplanes = 1,
+ .flags = FMT_FLAGS_M2M | FMT_HAS_ALPHA,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB555,
+ .depth = { 16 },
+ .color = FIMC_FMT_RGB555,
+ .memplanes = 1,
+ .colplanes = 1,
+ .flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB444,
+ .depth = { 16 },
+ .color = FIMC_FMT_RGB444,
+ .memplanes = 1,
+ .colplanes = 1,
+ .flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA,
+ }, {
+ .mbus_code = MEDIA_BUS_FMT_YUV10_1X30,
+ .flags = FMT_FLAGS_WRITEBACK,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCBYCR422,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .depth = { 16 },
+ .color = FIMC_FMT_CBYCRY422,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .depth = { 16 },
+ .color = FIMC_FMT_CRYCBY422,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCRYCB422,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+ .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUV422P,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCBYCR422,
+ .memplanes = 1,
+ .colplanes = 3,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCBYCR422,
+ .memplanes = 1,
+ .colplanes = 2,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV61,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCRYCB422,
+ .memplanes = 1,
+ .colplanes = 2,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .depth = { 12 },
+ .color = FIMC_FMT_YCBCR420,
+ .memplanes = 1,
+ .colplanes = 3,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = { 12 },
+ .color = FIMC_FMT_YCBCR420,
+ .memplanes = 1,
+ .colplanes = 2,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .color = FIMC_FMT_YCBCR420,
+ .depth = { 8, 4 },
+ .memplanes = 2,
+ .colplanes = 2,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUV420M,
+ .color = FIMC_FMT_YCBCR420,
+ .depth = { 8, 2, 2 },
+ .memplanes = 3,
+ .colplanes = 3,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_NV12MT,
+ .color = FIMC_FMT_YCBCR420,
+ .depth = { 8, 4 },
+ .memplanes = 2,
+ .colplanes = 2,
+ .flags = FMT_FLAGS_M2M,
+ }, {
+ .fourcc = V4L2_PIX_FMT_JPEG,
+ .color = FIMC_FMT_JPEG,
+ .depth = { 8 },
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_JPEG_1X8,
+ .flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED,
+ }, {
+ .fourcc = V4L2_PIX_FMT_S5C_UYVY_JPG,
+ .color = FIMC_FMT_YUYV_JPEG,
+ .depth = { 8 },
+ .memplanes = 2,
+ .colplanes = 1,
+ .mdataplanes = 0x2, /* plane 1 holds frame meta data */
+ .mbus_code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8,
+ .flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED,
+ },
+};
+
+struct fimc_fmt *fimc_get_format(unsigned int index)
+{
+ if (index >= ARRAY_SIZE(fimc_formats))
+ return NULL;
+
+ return &fimc_formats[index];
+}
+
+int fimc_check_scaler_ratio(struct fimc_ctx *ctx, int sw, int sh,
+ int dw, int dh, int rotation)
+{
+ if (rotation == 90 || rotation == 270)
+ swap(dw, dh);
+
+ if (!ctx->scaler.enabled)
+ return (sw == dw && sh == dh) ? 0 : -EINVAL;
+
+ if ((sw >= SCALER_MAX_HRATIO * dw) || (sh >= SCALER_MAX_VRATIO * dh))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift)
+{
+ u32 sh = 6;
+
+ if (src >= 64 * tar)
+ return -EINVAL;
+
+ while (sh--) {
+ u32 tmp = 1 << sh;
+ if (src >= tar * tmp) {
+ *shift = sh;
+ *ratio = tmp;
+ return 0;
+ }
+ }
+ *shift = 0;
+ *ratio = 1;
+ return 0;
+}
+
+int fimc_set_scaler_info(struct fimc_ctx *ctx)
+{
+ const struct fimc_variant *variant = ctx->fimc_dev->variant;
+ struct device *dev = &ctx->fimc_dev->pdev->dev;
+ struct fimc_scaler *sc = &ctx->scaler;
+ struct fimc_frame *s_frame = &ctx->s_frame;
+ struct fimc_frame *d_frame = &ctx->d_frame;
+ int tx, ty, sx, sy;
+ int ret;
+
+ if (ctx->rotation == 90 || ctx->rotation == 270) {
+ ty = d_frame->width;
+ tx = d_frame->height;
+ } else {
+ tx = d_frame->width;
+ ty = d_frame->height;
+ }
+ if (tx <= 0 || ty <= 0) {
+ dev_err(dev, "Invalid target size: %dx%d\n", tx, ty);
+ return -EINVAL;
+ }
+
+ sx = s_frame->width;
+ sy = s_frame->height;
+ if (sx <= 0 || sy <= 0) {
+ dev_err(dev, "Invalid source size: %dx%d\n", sx, sy);
+ return -EINVAL;
+ }
+ sc->real_width = sx;
+ sc->real_height = sy;
+
+ ret = fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor);
+ if (ret)
+ return ret;
+
+ ret = fimc_get_scaler_factor(sy, ty, &sc->pre_vratio, &sc->vfactor);
+ if (ret)
+ return ret;
+
+ sc->pre_dst_width = sx / sc->pre_hratio;
+ sc->pre_dst_height = sy / sc->pre_vratio;
+
+ if (variant->has_mainscaler_ext) {
+ sc->main_hratio = (sx << 14) / (tx << sc->hfactor);
+ sc->main_vratio = (sy << 14) / (ty << sc->vfactor);
+ } else {
+ sc->main_hratio = (sx << 8) / (tx << sc->hfactor);
+ sc->main_vratio = (sy << 8) / (ty << sc->vfactor);
+
+ }
+
+ sc->scaleup_h = (tx >= sx) ? 1 : 0;
+ sc->scaleup_v = (ty >= sy) ? 1 : 0;
+
+ /* check to see if input and output size/format differ */
+ if (s_frame->fmt->color == d_frame->fmt->color
+ && s_frame->width == d_frame->width
+ && s_frame->height == d_frame->height)
+ sc->copy_mode = 1;
+ else
+ sc->copy_mode = 0;
+
+ return 0;
+}
+
+static irqreturn_t fimc_irq_handler(int irq, void *priv)
+{
+ struct fimc_dev *fimc = priv;
+ struct fimc_ctx *ctx;
+
+ fimc_hw_clear_irq(fimc);
+
+ spin_lock(&fimc->slock);
+
+ if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) {
+ if (test_and_clear_bit(ST_M2M_SUSPENDING, &fimc->state)) {
+ set_bit(ST_M2M_SUSPENDED, &fimc->state);
+ wake_up(&fimc->irq_queue);
+ goto out;
+ }
+ ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev);
+ if (ctx != NULL) {
+ spin_unlock(&fimc->slock);
+ fimc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE);
+
+ if (ctx->state & FIMC_CTX_SHUT) {
+ ctx->state &= ~FIMC_CTX_SHUT;
+ wake_up(&fimc->irq_queue);
+ }
+ return IRQ_HANDLED;
+ }
+ } else if (test_bit(ST_CAPT_PEND, &fimc->state)) {
+ int last_buf = test_bit(ST_CAPT_JPEG, &fimc->state) &&
+ fimc->vid_cap.reqbufs_count == 1;
+ fimc_capture_irq_handler(fimc, !last_buf);
+ }
+out:
+ spin_unlock(&fimc->slock);
+ return IRQ_HANDLED;
+}
+
+/* The color format (colplanes, memplanes) must be already configured. */
+int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb,
+ struct fimc_frame *frame, struct fimc_addr *addr)
+{
+ int ret = 0;
+ u32 pix_size;
+
+ if (vb == NULL || frame == NULL)
+ return -EINVAL;
+
+ pix_size = frame->width * frame->height;
+
+ dbg("memplanes= %d, colplanes= %d, pix_size= %d",
+ frame->fmt->memplanes, frame->fmt->colplanes, pix_size);
+
+ addr->y = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ if (frame->fmt->memplanes == 1) {
+ switch (frame->fmt->colplanes) {
+ case 1:
+ addr->cb = 0;
+ addr->cr = 0;
+ break;
+ case 2:
+ /* decompose Y into Y/Cb */
+ addr->cb = (u32)(addr->y + pix_size);
+ addr->cr = 0;
+ break;
+ case 3:
+ addr->cb = (u32)(addr->y + pix_size);
+ /* decompose Y into Y/Cb/Cr */
+ if (FIMC_FMT_YCBCR420 == frame->fmt->color)
+ addr->cr = (u32)(addr->cb + (pix_size >> 2));
+ else /* 422 */
+ addr->cr = (u32)(addr->cb + (pix_size >> 1));
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else if (!frame->fmt->mdataplanes) {
+ if (frame->fmt->memplanes >= 2)
+ addr->cb = vb2_dma_contig_plane_dma_addr(vb, 1);
+
+ if (frame->fmt->memplanes == 3)
+ addr->cr = vb2_dma_contig_plane_dma_addr(vb, 2);
+ }
+
+ dbg("DMA ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d",
+ addr->y, addr->cb, addr->cr, ret);
+
+ return ret;
+}
+
+/* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */
+void fimc_set_yuv_order(struct fimc_ctx *ctx)
+{
+ /* The one only mode supported in SoC. */
+ ctx->in_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB;
+ ctx->out_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB;
+
+ /* Set order for 1 plane input formats. */
+ switch (ctx->s_frame.fmt->color) {
+ case FIMC_FMT_YCRYCB422:
+ ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCRYCB;
+ break;
+ case FIMC_FMT_CBYCRY422:
+ ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CBYCRY;
+ break;
+ case FIMC_FMT_CRYCBY422:
+ ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CRYCBY;
+ break;
+ case FIMC_FMT_YCBYCR422:
+ default:
+ ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCBYCR;
+ break;
+ }
+ dbg("ctx->in_order_1p= %d", ctx->in_order_1p);
+
+ switch (ctx->d_frame.fmt->color) {
+ case FIMC_FMT_YCRYCB422:
+ ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCRYCB;
+ break;
+ case FIMC_FMT_CBYCRY422:
+ ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CBYCRY;
+ break;
+ case FIMC_FMT_CRYCBY422:
+ ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CRYCBY;
+ break;
+ case FIMC_FMT_YCBYCR422:
+ default:
+ ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCBYCR;
+ break;
+ }
+ dbg("ctx->out_order_1p= %d", ctx->out_order_1p);
+}
+
+void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f)
+{
+ bool pix_hoff = ctx->fimc_dev->drv_data->dma_pix_hoff;
+ u32 i, depth = 0;
+
+ for (i = 0; i < f->fmt->memplanes; i++)
+ depth += f->fmt->depth[i];
+
+ f->dma_offset.y_h = f->offs_h;
+ if (!pix_hoff)
+ f->dma_offset.y_h *= (depth >> 3);
+
+ f->dma_offset.y_v = f->offs_v;
+
+ f->dma_offset.cb_h = f->offs_h;
+ f->dma_offset.cb_v = f->offs_v;
+
+ f->dma_offset.cr_h = f->offs_h;
+ f->dma_offset.cr_v = f->offs_v;
+
+ if (!pix_hoff) {
+ if (f->fmt->colplanes == 3) {
+ f->dma_offset.cb_h >>= 1;
+ f->dma_offset.cr_h >>= 1;
+ }
+ if (f->fmt->color == FIMC_FMT_YCBCR420) {
+ f->dma_offset.cb_v >>= 1;
+ f->dma_offset.cr_v >>= 1;
+ }
+ }
+
+ dbg("in_offset: color= %d, y_h= %d, y_v= %d",
+ f->fmt->color, f->dma_offset.y_h, f->dma_offset.y_v);
+}
+
+static int fimc_set_color_effect(struct fimc_ctx *ctx, enum v4l2_colorfx colorfx)
+{
+ struct fimc_effect *effect = &ctx->effect;
+
+ switch (colorfx) {
+ case V4L2_COLORFX_NONE:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
+ break;
+ case V4L2_COLORFX_BW:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
+ effect->pat_cb = 128;
+ effect->pat_cr = 128;
+ break;
+ case V4L2_COLORFX_SEPIA:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
+ effect->pat_cb = 115;
+ effect->pat_cr = 145;
+ break;
+ case V4L2_COLORFX_NEGATIVE:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_NEGATIVE;
+ break;
+ case V4L2_COLORFX_EMBOSS:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_EMBOSSING;
+ break;
+ case V4L2_COLORFX_ART_FREEZE:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_ARTFREEZE;
+ break;
+ case V4L2_COLORFX_SILHOUETTE:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_SILHOUETTE;
+ break;
+ case V4L2_COLORFX_SET_CBCR:
+ effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
+ effect->pat_cb = ctx->ctrls.colorfx_cbcr->val >> 8;
+ effect->pat_cr = ctx->ctrls.colorfx_cbcr->val & 0xff;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * V4L2 controls handling
+ */
+#define ctrl_to_ctx(__ctrl) \
+ container_of((__ctrl)->handler, struct fimc_ctx, ctrls.handler)
+
+static int __fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_ctrl *ctrl)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ const struct fimc_variant *variant = fimc->variant;
+ int ret = 0;
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ ctx->hflip = ctrl->val;
+ break;
+
+ case V4L2_CID_VFLIP:
+ ctx->vflip = ctrl->val;
+ break;
+
+ case V4L2_CID_ROTATE:
+ if (fimc_capture_pending(fimc)) {
+ ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width,
+ ctx->s_frame.height, ctx->d_frame.width,
+ ctx->d_frame.height, ctrl->val);
+ if (ret)
+ return -EINVAL;
+ }
+ if ((ctrl->val == 90 || ctrl->val == 270) &&
+ !variant->has_out_rot)
+ return -EINVAL;
+
+ ctx->rotation = ctrl->val;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ ctx->d_frame.alpha = ctrl->val;
+ break;
+
+ case V4L2_CID_COLORFX:
+ ret = fimc_set_color_effect(ctx, ctrl->val);
+ if (ret)
+ return ret;
+ break;
+ }
+
+ ctx->state |= FIMC_PARAMS;
+ set_bit(ST_CAPT_APPLY_CFG, &fimc->state);
+ return 0;
+}
+
+static int fimc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct fimc_ctx *ctx = ctrl_to_ctx(ctrl);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctx->fimc_dev->slock, flags);
+ ret = __fimc_s_ctrl(ctx, ctrl);
+ spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops fimc_ctrl_ops = {
+ .s_ctrl = fimc_s_ctrl,
+};
+
+int fimc_ctrls_create(struct fimc_ctx *ctx)
+{
+ unsigned int max_alpha = fimc_get_alpha_mask(ctx->d_frame.fmt);
+ struct fimc_ctrls *ctrls = &ctx->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+
+ if (ctx->ctrls.ready)
+ return 0;
+
+ v4l2_ctrl_handler_init(handler, 6);
+
+ ctrls->rotate = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
+ V4L2_CID_ROTATE, 0, 270, 90, 0);
+ ctrls->hflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ if (ctx->fimc_dev->drv_data->alpha_color)
+ ctrls->alpha = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
+ V4L2_CID_ALPHA_COMPONENT,
+ 0, max_alpha, 1, 0);
+ else
+ ctrls->alpha = NULL;
+
+ ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, &fimc_ctrl_ops,
+ V4L2_CID_COLORFX, V4L2_COLORFX_SET_CBCR,
+ ~0x983f, V4L2_COLORFX_NONE);
+
+ ctrls->colorfx_cbcr = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
+ V4L2_CID_COLORFX_CBCR, 0, 0xffff, 1, 0);
+
+ ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
+
+ if (!handler->error) {
+ v4l2_ctrl_cluster(2, &ctrls->colorfx);
+ ctrls->ready = true;
+ }
+
+ return handler->error;
+}
+
+void fimc_ctrls_delete(struct fimc_ctx *ctx)
+{
+ struct fimc_ctrls *ctrls = &ctx->ctrls;
+
+ if (ctrls->ready) {
+ v4l2_ctrl_handler_free(&ctrls->handler);
+ ctrls->ready = false;
+ ctrls->alpha = NULL;
+ }
+}
+
+void fimc_ctrls_activate(struct fimc_ctx *ctx, bool active)
+{
+ unsigned int has_alpha = ctx->d_frame.fmt->flags & FMT_HAS_ALPHA;
+ struct fimc_ctrls *ctrls = &ctx->ctrls;
+
+ if (!ctrls->ready)
+ return;
+
+ mutex_lock(ctrls->handler.lock);
+ v4l2_ctrl_activate(ctrls->rotate, active);
+ v4l2_ctrl_activate(ctrls->hflip, active);
+ v4l2_ctrl_activate(ctrls->vflip, active);
+ v4l2_ctrl_activate(ctrls->colorfx, active);
+ if (ctrls->alpha)
+ v4l2_ctrl_activate(ctrls->alpha, active && has_alpha);
+
+ if (active) {
+ fimc_set_color_effect(ctx, ctrls->colorfx->cur.val);
+ ctx->rotation = ctrls->rotate->val;
+ ctx->hflip = ctrls->hflip->val;
+ ctx->vflip = ctrls->vflip->val;
+ } else {
+ ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
+ ctx->rotation = 0;
+ ctx->hflip = 0;
+ ctx->vflip = 0;
+ }
+ mutex_unlock(ctrls->handler.lock);
+}
+
+/* Update maximum value of the alpha color control */
+void fimc_alpha_ctrl_update(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct v4l2_ctrl *ctrl = ctx->ctrls.alpha;
+
+ if (ctrl == NULL || !fimc->drv_data->alpha_color)
+ return;
+
+ v4l2_ctrl_lock(ctrl);
+ ctrl->maximum = fimc_get_alpha_mask(ctx->d_frame.fmt);
+
+ if (ctrl->cur.val > ctrl->maximum)
+ ctrl->cur.val = ctrl->maximum;
+
+ v4l2_ctrl_unlock(ctrl);
+}
+
+void __fimc_get_format(struct fimc_frame *frame, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
+ int i;
+
+ pixm->width = frame->o_width;
+ pixm->height = frame->o_height;
+ pixm->field = V4L2_FIELD_NONE;
+ pixm->pixelformat = frame->fmt->fourcc;
+ pixm->colorspace = V4L2_COLORSPACE_JPEG;
+ pixm->num_planes = frame->fmt->memplanes;
+
+ for (i = 0; i < pixm->num_planes; ++i) {
+ pixm->plane_fmt[i].bytesperline = frame->bytesperline[i];
+ pixm->plane_fmt[i].sizeimage = frame->payload[i];
+ }
+}
+
+/**
+ * fimc_adjust_mplane_format - adjust bytesperline/sizeimage for each plane
+ * @fmt: fimc pixel format description (input)
+ * @width: requested pixel width
+ * @height: requested pixel height
+ * @pix: multi-plane format to adjust
+ */
+void fimc_adjust_mplane_format(struct fimc_fmt *fmt, u32 width, u32 height,
+ struct v4l2_pix_format_mplane *pix)
+{
+ u32 bytesperline = 0;
+ int i;
+
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ pix->field = V4L2_FIELD_NONE;
+ pix->num_planes = fmt->memplanes;
+ pix->pixelformat = fmt->fourcc;
+ pix->height = height;
+ pix->width = width;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[i];
+ u32 bpl = plane_fmt->bytesperline;
+ u32 sizeimage;
+
+ if (fmt->colplanes > 1 && (bpl == 0 || bpl < pix->width))
+ bpl = pix->width; /* Planar */
+
+ if (fmt->colplanes == 1 && /* Packed */
+ (bpl == 0 || ((bpl * 8) / fmt->depth[i]) < pix->width))
+ bpl = (pix->width * fmt->depth[0]) / 8;
+ /*
+ * Currently bytesperline for each plane is same, except
+ * V4L2_PIX_FMT_YUV420M format. This calculation may need
+ * to be changed when other multi-planar formats are added
+ * to the fimc_formats[] array.
+ */
+ if (i == 0)
+ bytesperline = bpl;
+ else if (i == 1 && fmt->memplanes == 3)
+ bytesperline /= 2;
+
+ plane_fmt->bytesperline = bytesperline;
+ sizeimage = pix->width * pix->height * fmt->depth[i] / 8;
+
+ /* Ensure full last row for tiled formats */
+ if (tiled_fmt(fmt)) {
+ /* 64 * 32 * plane_fmt->bytesperline / 64 */
+ u32 row_size = plane_fmt->bytesperline * 32;
+
+ sizeimage = roundup(sizeimage, row_size);
+ }
+
+ plane_fmt->sizeimage = max(sizeimage, plane_fmt->sizeimage);
+ }
+}
+
+/**
+ * fimc_find_format - lookup fimc color format by fourcc or media bus format
+ * @pixelformat: fourcc to match, ignored if null
+ * @mbus_code: media bus code to match, ignored if null
+ * @mask: the color flags to match
+ * @index: offset in the fimc_formats array, ignored if negative
+ */
+struct fimc_fmt *fimc_find_format(const u32 *pixelformat, const u32 *mbus_code,
+ unsigned int mask, int index)
+{
+ struct fimc_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)ARRAY_SIZE(fimc_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) {
+ fmt = &fimc_formats[i];
+ if (!(fmt->flags & mask))
+ continue;
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+static void fimc_clk_put(struct fimc_dev *fimc)
+{
+ int i;
+ for (i = 0; i < MAX_FIMC_CLOCKS; i++) {
+ if (IS_ERR(fimc->clock[i]))
+ continue;
+ clk_unprepare(fimc->clock[i]);
+ clk_put(fimc->clock[i]);
+ fimc->clock[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int fimc_clk_get(struct fimc_dev *fimc)
+{
+ int i, ret;
+
+ for (i = 0; i < MAX_FIMC_CLOCKS; i++)
+ fimc->clock[i] = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < MAX_FIMC_CLOCKS; i++) {
+ fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]);
+ if (IS_ERR(fimc->clock[i])) {
+ ret = PTR_ERR(fimc->clock[i]);
+ goto err;
+ }
+ ret = clk_prepare(fimc->clock[i]);
+ if (ret < 0) {
+ clk_put(fimc->clock[i]);
+ fimc->clock[i] = ERR_PTR(-EINVAL);
+ goto err;
+ }
+ }
+ return 0;
+err:
+ fimc_clk_put(fimc);
+ dev_err(&fimc->pdev->dev, "failed to get clock: %s\n",
+ fimc_clocks[i]);
+ return -ENXIO;
+}
+
+#ifdef CONFIG_PM
+static int fimc_m2m_suspend(struct fimc_dev *fimc)
+{
+ unsigned long flags;
+ int timeout;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ if (!fimc_m2m_pending(fimc)) {
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return 0;
+ }
+ clear_bit(ST_M2M_SUSPENDED, &fimc->state);
+ set_bit(ST_M2M_SUSPENDING, &fimc->state);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ timeout = wait_event_timeout(fimc->irq_queue,
+ test_bit(ST_M2M_SUSPENDED, &fimc->state),
+ FIMC_SHUTDOWN_TIMEOUT);
+
+ clear_bit(ST_M2M_SUSPENDING, &fimc->state);
+ return timeout == 0 ? -EAGAIN : 0;
+}
+
+static int fimc_m2m_resume(struct fimc_dev *fimc)
+{
+ struct fimc_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ /* Clear for full H/W setup in first run after resume */
+ ctx = fimc->m2m.ctx;
+ fimc->m2m.ctx = NULL;
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (test_and_clear_bit(ST_M2M_SUSPENDED, &fimc->state))
+ fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct of_device_id fimc_of_match[];
+
+static int fimc_parse_dt(struct fimc_dev *fimc, u32 *clk_freq)
+{
+ struct device *dev = &fimc->pdev->dev;
+ struct device_node *node = dev->of_node;
+ const struct of_device_id *of_id;
+ struct fimc_variant *v;
+ struct fimc_pix_limit *lim;
+ u32 args[FIMC_PIX_LIMITS_MAX];
+ int ret;
+
+ if (of_property_read_bool(node, "samsung,lcd-wb"))
+ return -ENODEV;
+
+ v = devm_kzalloc(dev, sizeof(*v) + sizeof(*lim), GFP_KERNEL);
+ if (!v)
+ return -ENOMEM;
+
+ of_id = of_match_node(fimc_of_match, node);
+ if (!of_id)
+ return -EINVAL;
+ fimc->drv_data = of_id->data;
+ ret = of_property_read_u32_array(node, "samsung,pix-limits",
+ args, FIMC_PIX_LIMITS_MAX);
+ if (ret < 0)
+ return ret;
+
+ lim = (struct fimc_pix_limit *)&v[1];
+
+ lim->scaler_en_w = args[0];
+ lim->scaler_dis_w = args[1];
+ lim->out_rot_en_w = args[2];
+ lim->out_rot_dis_w = args[3];
+ v->pix_limit = lim;
+
+ ret = of_property_read_u32_array(node, "samsung,min-pix-sizes",
+ args, 2);
+ v->min_inp_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[0];
+ v->min_out_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[1];
+ ret = of_property_read_u32_array(node, "samsung,min-pix-alignment",
+ args, 2);
+ v->min_vsize_align = ret ? FIMC_DEF_HEIGHT_ALIGN : args[0];
+ v->hor_offs_align = ret ? FIMC_DEF_HOR_OFFS_ALIGN : args[1];
+
+ ret = of_property_read_u32(node, "samsung,rotators", &args[1]);
+ v->has_inp_rot = ret ? 1 : args[1] & 0x01;
+ v->has_out_rot = ret ? 1 : args[1] & 0x10;
+ v->has_mainscaler_ext = of_property_read_bool(node,
+ "samsung,mainscaler-ext");
+
+ v->has_isp_wb = of_property_read_bool(node, "samsung,isp-wb");
+ v->has_cam_if = of_property_read_bool(node, "samsung,cam-if");
+ of_property_read_u32(node, "clock-frequency", clk_freq);
+ fimc->id = of_alias_get_id(node, "fimc");
+
+ fimc->variant = v;
+ return 0;
+}
+
+static int fimc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ u32 lclk_freq = 0;
+ struct fimc_dev *fimc;
+ int ret = 0;
+ int irq;
+
+ fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL);
+ if (!fimc)
+ return -ENOMEM;
+
+ fimc->pdev = pdev;
+
+ if (dev->of_node) {
+ ret = fimc_parse_dt(fimc, &lclk_freq);
+ if (ret < 0)
+ return ret;
+ } else {
+ fimc->drv_data = fimc_get_drvdata(pdev);
+ fimc->id = pdev->id;
+ }
+ if (!fimc->drv_data || fimc->id >= fimc->drv_data->num_entities ||
+ fimc->id < 0) {
+ dev_err(dev, "Invalid driver data or device id (%d)\n",
+ fimc->id);
+ return -EINVAL;
+ }
+ if (!dev->of_node)
+ fimc->variant = fimc->drv_data->variant[fimc->id];
+
+ init_waitqueue_head(&fimc->irq_queue);
+ spin_lock_init(&fimc->slock);
+ mutex_init(&fimc->lock);
+
+ if (fimc->variant->has_isp_wb) {
+ fimc->sysreg = fimc_get_sysreg_regmap(dev->of_node);
+ if (IS_ERR(fimc->sysreg))
+ return PTR_ERR(fimc->sysreg);
+ }
+
+ fimc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(fimc->regs))
+ return PTR_ERR(fimc->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = fimc_clk_get(fimc);
+ if (ret)
+ return ret;
+
+ if (lclk_freq == 0)
+ lclk_freq = fimc->drv_data->lclk_frequency;
+
+ ret = clk_set_rate(fimc->clock[CLK_BUS], lclk_freq);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_enable(fimc->clock[CLK_BUS]);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(dev, irq, fimc_irq_handler,
+ 0, dev_name(dev), fimc);
+ if (ret < 0) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ goto err_sclk;
+ }
+
+ ret = fimc_initialize_capture_subdev(fimc);
+ if (ret < 0)
+ goto err_sclk;
+
+ platform_set_drvdata(pdev, fimc);
+ pm_runtime_enable(dev);
+
+ if (!pm_runtime_enabled(dev)) {
+ ret = clk_enable(fimc->clock[CLK_GATE]);
+ if (ret < 0)
+ goto err_sd;
+ }
+
+ vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id);
+ return 0;
+
+err_sd:
+ fimc_unregister_capture_subdev(fimc);
+err_sclk:
+ clk_disable(fimc->clock[CLK_BUS]);
+ fimc_clk_put(fimc);
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int fimc_runtime_resume(struct device *dev)
+{
+ struct fimc_dev *fimc = dev_get_drvdata(dev);
+
+ dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state);
+
+ /* Enable clocks and perform basic initialization */
+ clk_enable(fimc->clock[CLK_GATE]);
+ fimc_hw_reset(fimc);
+
+ /* Resume the capture or mem-to-mem device */
+ if (fimc_capture_busy(fimc))
+ return fimc_capture_resume(fimc);
+
+ return fimc_m2m_resume(fimc);
+}
+
+static int fimc_runtime_suspend(struct device *dev)
+{
+ struct fimc_dev *fimc = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (fimc_capture_busy(fimc))
+ ret = fimc_capture_suspend(fimc);
+ else
+ ret = fimc_m2m_suspend(fimc);
+ if (!ret)
+ clk_disable(fimc->clock[CLK_GATE]);
+
+ dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state);
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int fimc_resume(struct device *dev)
+{
+ struct fimc_dev *fimc = dev_get_drvdata(dev);
+ unsigned long flags;
+
+ dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state);
+
+ /* Do not resume if the device was idle before system suspend */
+ spin_lock_irqsave(&fimc->slock, flags);
+ if (!test_and_clear_bit(ST_LPM, &fimc->state) ||
+ (!fimc_m2m_active(fimc) && !fimc_capture_busy(fimc))) {
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return 0;
+ }
+ fimc_hw_reset(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (fimc_capture_busy(fimc))
+ return fimc_capture_resume(fimc);
+
+ return fimc_m2m_resume(fimc);
+}
+
+static int fimc_suspend(struct device *dev)
+{
+ struct fimc_dev *fimc = dev_get_drvdata(dev);
+
+ dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state);
+
+ if (test_and_set_bit(ST_LPM, &fimc->state))
+ return 0;
+ if (fimc_capture_busy(fimc))
+ return fimc_capture_suspend(fimc);
+
+ return fimc_m2m_suspend(fimc);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static void fimc_remove(struct platform_device *pdev)
+{
+ struct fimc_dev *fimc = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+ if (!pm_runtime_status_suspended(&pdev->dev))
+ clk_disable(fimc->clock[CLK_GATE]);
+ pm_runtime_set_suspended(&pdev->dev);
+
+ fimc_unregister_capture_subdev(fimc);
+ vb2_dma_contig_clear_max_seg_size(&pdev->dev);
+
+ clk_disable(fimc->clock[CLK_BUS]);
+ fimc_clk_put(fimc);
+
+ dev_info(&pdev->dev, "driver unloaded\n");
+}
+
+/* S5PV210, S5PC110 */
+static const struct fimc_drvdata fimc_drvdata_s5pv210 = {
+ .num_entities = 3,
+ .lclk_frequency = 166000000UL,
+ .out_buf_count = 4,
+ .dma_pix_hoff = 1,
+};
+
+/* EXYNOS4210, S5PV310, S5PC210 */
+static const struct fimc_drvdata fimc_drvdata_exynos4210 = {
+ .num_entities = 4,
+ .lclk_frequency = 166000000UL,
+ .dma_pix_hoff = 1,
+ .cistatus2 = 1,
+ .alpha_color = 1,
+ .out_buf_count = 32,
+};
+
+/* EXYNOS4212, EXYNOS4412 */
+static const struct fimc_drvdata fimc_drvdata_exynos4x12 = {
+ .num_entities = 4,
+ .lclk_frequency = 166000000UL,
+ .dma_pix_hoff = 1,
+ .cistatus2 = 1,
+ .alpha_color = 1,
+ .out_buf_count = 32,
+};
+
+static const struct of_device_id fimc_of_match[] = {
+ {
+ .compatible = "samsung,s5pv210-fimc",
+ .data = &fimc_drvdata_s5pv210,
+ }, {
+ .compatible = "samsung,exynos4210-fimc",
+ .data = &fimc_drvdata_exynos4210,
+ }, {
+ .compatible = "samsung,exynos4212-fimc",
+ .data = &fimc_drvdata_exynos4x12,
+ },
+ { /* sentinel */ },
+};
+
+static const struct dev_pm_ops fimc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fimc_suspend, fimc_resume)
+ SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL)
+};
+
+static struct platform_driver fimc_driver = {
+ .probe = fimc_probe,
+ .remove_new = fimc_remove,
+ .driver = {
+ .of_match_table = fimc_of_match,
+ .name = FIMC_DRIVER_NAME,
+ .pm = &fimc_pm_ops,
+ }
+};
+
+int __init fimc_register_driver(void)
+{
+ return platform_driver_register(&fimc_driver);
+}
+
+void fimc_unregister_driver(void)
+{
+ platform_driver_unregister(&fimc_driver);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.h b/drivers/media/platform/samsung/exynos4-is/fimc-core.h
new file mode 100644
index 0000000000..2b0760add0
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.h
@@ -0,0 +1,725 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef FIMC_CORE_H_
+#define FIMC_CORE_H_
+
+/*#define DEBUG*/
+
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/syscon.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+
+#include <media/media-entity.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-mediabus.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#define dbg(fmt, args...) \
+ pr_debug("%s:%d: " fmt "\n", __func__, __LINE__, ##args)
+
+/* Time to wait for next frame VSYNC interrupt while stopping operation. */
+#define FIMC_SHUTDOWN_TIMEOUT ((100*HZ)/1000)
+#define MAX_FIMC_CLOCKS 2
+#define FIMC_DRIVER_NAME "exynos4-fimc"
+#define FIMC_MAX_DEVS 4
+#define FIMC_MAX_OUT_BUFS 4
+#define SCALER_MAX_HRATIO 64
+#define SCALER_MAX_VRATIO 64
+#define DMA_MIN_SIZE 8
+#define FIMC_CAMIF_MAX_HEIGHT 0x2000
+#define FIMC_MAX_JPEG_BUF_SIZE (10 * SZ_1M)
+#define FIMC_MAX_PLANES 3
+#define FIMC_PIX_LIMITS_MAX 4
+#define FIMC_DEF_MIN_SIZE 16
+#define FIMC_DEF_HEIGHT_ALIGN 2
+#define FIMC_DEF_HOR_OFFS_ALIGN 1
+#define FIMC_DEFAULT_WIDTH 640
+#define FIMC_DEFAULT_HEIGHT 480
+
+/* indices to the clocks array */
+enum {
+ CLK_BUS,
+ CLK_GATE,
+};
+
+enum fimc_dev_flags {
+ ST_LPM,
+ /* m2m node */
+ ST_M2M_RUN,
+ ST_M2M_PEND,
+ ST_M2M_SUSPENDING,
+ ST_M2M_SUSPENDED,
+ /* capture node */
+ ST_CAPT_PEND,
+ ST_CAPT_RUN,
+ ST_CAPT_STREAM,
+ ST_CAPT_ISP_STREAM,
+ ST_CAPT_SUSPENDED,
+ ST_CAPT_SHUT,
+ ST_CAPT_BUSY,
+ ST_CAPT_APPLY_CFG,
+ ST_CAPT_JPEG,
+};
+
+#define fimc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state)
+#define fimc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state)
+
+#define fimc_capture_running(dev) test_bit(ST_CAPT_RUN, &(dev)->state)
+#define fimc_capture_pending(dev) test_bit(ST_CAPT_PEND, &(dev)->state)
+#define fimc_capture_busy(dev) test_bit(ST_CAPT_BUSY, &(dev)->state)
+
+enum fimc_datapath {
+ FIMC_IO_NONE,
+ FIMC_IO_CAMERA,
+ FIMC_IO_DMA,
+ FIMC_IO_LCDFIFO,
+ FIMC_IO_WRITEBACK,
+ FIMC_IO_ISP,
+};
+
+enum fimc_color_fmt {
+ FIMC_FMT_RGB444 = 0x10,
+ FIMC_FMT_RGB555,
+ FIMC_FMT_RGB565,
+ FIMC_FMT_RGB666,
+ FIMC_FMT_RGB888,
+ FIMC_FMT_RGB30_LOCAL,
+ FIMC_FMT_YCBCR420 = 0x20,
+ FIMC_FMT_YCBYCR422,
+ FIMC_FMT_YCRYCB422,
+ FIMC_FMT_CBYCRY422,
+ FIMC_FMT_CRYCBY422,
+ FIMC_FMT_YCBCR444_LOCAL,
+ FIMC_FMT_RAW8 = 0x40,
+ FIMC_FMT_RAW10,
+ FIMC_FMT_RAW12,
+ FIMC_FMT_JPEG = 0x80,
+ FIMC_FMT_YUYV_JPEG = 0x100,
+};
+
+#define fimc_fmt_is_user_defined(x) (!!((x) & 0x180))
+#define fimc_fmt_is_rgb(x) (!!((x) & 0x10))
+
+#define IS_M2M(__strt) ((__strt) == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || \
+ __strt == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+
+/* The hardware context state. */
+#define FIMC_PARAMS (1 << 0)
+#define FIMC_COMPOSE (1 << 1)
+#define FIMC_CTX_M2M (1 << 16)
+#define FIMC_CTX_CAP (1 << 17)
+#define FIMC_CTX_SHUT (1 << 18)
+
+/* Image conversion flags */
+#define FIMC_IN_DMA_ACCESS_TILED (1 << 0)
+#define FIMC_IN_DMA_ACCESS_LINEAR (0 << 0)
+#define FIMC_OUT_DMA_ACCESS_TILED (1 << 1)
+#define FIMC_OUT_DMA_ACCESS_LINEAR (0 << 1)
+#define FIMC_SCAN_MODE_PROGRESSIVE (0 << 2)
+#define FIMC_SCAN_MODE_INTERLACED (1 << 2)
+/*
+ * YCbCr data dynamic range for RGB-YUV color conversion.
+ * Y/Cb/Cr: (0 ~ 255) */
+#define FIMC_COLOR_RANGE_WIDE (0 << 3)
+/* Y (16 ~ 235), Cb/Cr (16 ~ 240) */
+#define FIMC_COLOR_RANGE_NARROW (1 << 3)
+
+/**
+ * struct fimc_dma_offset - pixel offset information for DMA
+ * @y_h: y value horizontal offset
+ * @y_v: y value vertical offset
+ * @cb_h: cb value horizontal offset
+ * @cb_v: cb value vertical offset
+ * @cr_h: cr value horizontal offset
+ * @cr_v: cr value vertical offset
+ */
+struct fimc_dma_offset {
+ int y_h;
+ int y_v;
+ int cb_h;
+ int cb_v;
+ int cr_h;
+ int cr_v;
+};
+
+/**
+ * struct fimc_effect - color effect information
+ * @type: effect type
+ * @pat_cb: cr value when type is "arbitrary"
+ * @pat_cr: cr value when type is "arbitrary"
+ */
+struct fimc_effect {
+ u32 type;
+ u8 pat_cb;
+ u8 pat_cr;
+};
+
+/**
+ * struct fimc_scaler - the configuration data for FIMC inetrnal scaler
+ * @scaleup_h: flag indicating scaling up horizontally
+ * @scaleup_v: flag indicating scaling up vertically
+ * @copy_mode: flag indicating transparent DMA transfer (no scaling
+ * and color format conversion)
+ * @enabled: flag indicating if the scaler is used
+ * @hfactor: horizontal shift factor
+ * @vfactor: vertical shift factor
+ * @pre_hratio: horizontal ratio of the prescaler
+ * @pre_vratio: vertical ratio of the prescaler
+ * @pre_dst_width: the prescaler's destination width
+ * @pre_dst_height: the prescaler's destination height
+ * @main_hratio: the main scaler's horizontal ratio
+ * @main_vratio: the main scaler's vertical ratio
+ * @real_width: source pixel (width - offset)
+ * @real_height: source pixel (height - offset)
+ */
+struct fimc_scaler {
+ unsigned int scaleup_h:1;
+ unsigned int scaleup_v:1;
+ unsigned int copy_mode:1;
+ unsigned int enabled:1;
+ u32 hfactor;
+ u32 vfactor;
+ u32 pre_hratio;
+ u32 pre_vratio;
+ u32 pre_dst_width;
+ u32 pre_dst_height;
+ u32 main_hratio;
+ u32 main_vratio;
+ u32 real_width;
+ u32 real_height;
+};
+
+/**
+ * struct fimc_addr - the FIMC address set for DMA
+ * @y: luminance plane address
+ * @cb: Cb plane address
+ * @cr: Cr plane address
+ */
+struct fimc_addr {
+ u32 y;
+ u32 cb;
+ u32 cr;
+};
+
+/**
+ * struct fimc_vid_buffer - the driver's video buffer
+ * @vb: v4l vb2 buffer
+ * @list: linked list structure for buffer queue
+ * @addr: precalculated DMA address set
+ * @index: buffer index for the output DMA engine
+ */
+struct fimc_vid_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ struct fimc_addr addr;
+ int index;
+};
+
+/**
+ * struct fimc_frame - source/target frame properties
+ * @f_width: image full width (virtual screen size)
+ * @f_height: image full height (virtual screen size)
+ * @o_width: original image width as set by S_FMT
+ * @o_height: original image height as set by S_FMT
+ * @offs_h: image horizontal pixel offset
+ * @offs_v: image vertical pixel offset
+ * @width: image pixel width
+ * @height: image pixel weight
+ * @payload: image size in bytes (w x h x bpp)
+ * @bytesperline: bytesperline value for each plane
+ * @addr: image frame buffer DMA addresses
+ * @dma_offset: DMA offset in bytes
+ * @fmt: fimc color format pointer
+ * @alpha: alpha value
+ */
+struct fimc_frame {
+ u32 f_width;
+ u32 f_height;
+ u32 o_width;
+ u32 o_height;
+ u32 offs_h;
+ u32 offs_v;
+ u32 width;
+ u32 height;
+ unsigned int payload[VIDEO_MAX_PLANES];
+ unsigned int bytesperline[VIDEO_MAX_PLANES];
+ struct fimc_addr addr;
+ struct fimc_dma_offset dma_offset;
+ struct fimc_fmt *fmt;
+ u8 alpha;
+};
+
+/**
+ * struct fimc_m2m_device - v4l2 memory-to-memory device data
+ * @vfd: the video device node for v4l2 m2m mode
+ * @m2m_dev: v4l2 memory-to-memory device data
+ * @ctx: hardware context data
+ * @refcnt: the reference counter
+ */
+struct fimc_m2m_device {
+ struct video_device vfd;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct fimc_ctx *ctx;
+ int refcnt;
+};
+
+#define FIMC_SD_PAD_SINK_CAM 0
+#define FIMC_SD_PAD_SINK_FIFO 1
+#define FIMC_SD_PAD_SOURCE 2
+#define FIMC_SD_PADS_NUM 3
+
+/**
+ * struct fimc_vid_cap - camera capture device information
+ * @ctx: hardware context data
+ * @subdev: subdev exposing the FIMC processing block
+ * @ve: exynos video device entity structure
+ * @vd_pad: fimc video capture node pad
+ * @sd_pads: fimc video processing block pads
+ * @ci_fmt: image format at the FIMC camera input (and the scaler output)
+ * @wb_fmt: image format at the FIMC ISP Writeback input
+ * @source_config: external image source related configuration structure
+ * @pending_buf_q: the pending buffer queue head
+ * @active_buf_q: the queue head of buffers scheduled in hardware
+ * @vbq: the capture am video buffer queue
+ * @active_buf_cnt: number of video buffers scheduled in hardware
+ * @buf_index: index for managing the output DMA buffers
+ * @frame_count: the frame counter for statistics
+ * @reqbufs_count: the number of buffers requested in REQBUFS ioctl
+ * @streaming: is streaming in progress?
+ * @input: capture input type, grp_id of the attached subdev
+ * @user_subdev_api: true if subdevs are not configured by the host driver
+ */
+struct fimc_vid_cap {
+ struct fimc_ctx *ctx;
+ struct v4l2_subdev subdev;
+ struct exynos_video_entity ve;
+ struct media_pad vd_pad;
+ struct media_pad sd_pads[FIMC_SD_PADS_NUM];
+ struct v4l2_mbus_framefmt ci_fmt;
+ struct v4l2_mbus_framefmt wb_fmt;
+ struct fimc_source_info source_config;
+ struct list_head pending_buf_q;
+ struct list_head active_buf_q;
+ struct vb2_queue vbq;
+ int active_buf_cnt;
+ int buf_index;
+ unsigned int frame_count;
+ unsigned int reqbufs_count;
+ bool streaming;
+ u32 input;
+ bool user_subdev_api;
+};
+
+/**
+ * struct fimc_pix_limit - image pixel size limits in various IP configurations
+ *
+ * @scaler_en_w: max input pixel width when the scaler is enabled
+ * @scaler_dis_w: max input pixel width when the scaler is disabled
+ * @in_rot_en_h: max input width with the input rotator is on
+ * @in_rot_dis_w: max input width with the input rotator is off
+ * @out_rot_en_w: max output width with the output rotator on
+ * @out_rot_dis_w: max output width with the output rotator off
+ */
+struct fimc_pix_limit {
+ u16 scaler_en_w;
+ u16 scaler_dis_w;
+ u16 in_rot_en_h;
+ u16 in_rot_dis_w;
+ u16 out_rot_en_w;
+ u16 out_rot_dis_w;
+};
+
+/**
+ * struct fimc_variant - FIMC device variant information
+ * @has_inp_rot: set if has input rotator
+ * @has_out_rot: set if has output rotator
+ * @has_mainscaler_ext: 1 if extended mainscaler ratios in CIEXTEN register
+ * are present in this IP revision
+ * @has_cam_if: set if this instance has a camera input interface
+ * @has_isp_wb: set if this instance has ISP writeback input
+ * @pix_limit: pixel size constraints for the scaler
+ * @min_inp_pixsize: minimum input pixel size
+ * @min_out_pixsize: minimum output pixel size
+ * @hor_offs_align: horizontal pixel offset alignment
+ * @min_vsize_align: minimum vertical pixel size alignment
+ */
+struct fimc_variant {
+ unsigned int has_inp_rot:1;
+ unsigned int has_out_rot:1;
+ unsigned int has_mainscaler_ext:1;
+ unsigned int has_cam_if:1;
+ unsigned int has_isp_wb:1;
+ const struct fimc_pix_limit *pix_limit;
+ u16 min_inp_pixsize;
+ u16 min_out_pixsize;
+ u16 hor_offs_align;
+ u16 min_vsize_align;
+};
+
+/**
+ * struct fimc_drvdata - per device type driver data
+ * @variant: variant information for this device
+ * @num_entities: number of fimc instances available in a SoC
+ * @lclk_frequency: local bus clock frequency
+ * @cistatus2: 1 if the FIMC IPs have CISTATUS2 register
+ * @dma_pix_hoff: the horizontal DMA offset unit: 1 - pixels, 0 - bytes
+ * @alpha_color: 1 if alpha color component is supported
+ * @out_buf_count: maximum number of output DMA buffers supported
+ */
+struct fimc_drvdata {
+ const struct fimc_variant *variant[FIMC_MAX_DEVS];
+ int num_entities;
+ unsigned long lclk_frequency;
+ /* Fields common to all FIMC IP instances */
+ u8 cistatus2;
+ u8 dma_pix_hoff;
+ u8 alpha_color;
+ u8 out_buf_count;
+};
+
+#define fimc_get_drvdata(_pdev) \
+ ((struct fimc_drvdata *) platform_get_device_id(_pdev)->driver_data)
+
+struct fimc_ctx;
+
+/**
+ * struct fimc_dev - abstraction for FIMC entity
+ * @slock: the spinlock protecting this data structure
+ * @lock: the mutex protecting this data structure
+ * @pdev: pointer to the FIMC platform device
+ * @pdata: pointer to the device platform data
+ * @sysreg: pointer to the SYSREG regmap
+ * @variant: the IP variant information
+ * @drv_data: driver data
+ * @id: FIMC device index (0..FIMC_MAX_DEVS)
+ * @clock: clocks required for FIMC operation
+ * @regs: the mapped hardware registers
+ * @irq_queue: interrupt handler waitqueue
+ * @v4l2_dev: root v4l2_device
+ * @m2m: memory-to-memory V4L2 device information
+ * @vid_cap: camera capture device information
+ * @state: flags used to synchronize m2m and capture mode operation
+ */
+struct fimc_dev {
+ spinlock_t slock;
+ struct mutex lock;
+ struct platform_device *pdev;
+ struct s5p_platform_fimc *pdata;
+ struct regmap *sysreg;
+ const struct fimc_variant *variant;
+ const struct fimc_drvdata *drv_data;
+ int id;
+ struct clk *clock[MAX_FIMC_CLOCKS];
+ void __iomem *regs;
+ wait_queue_head_t irq_queue;
+ struct v4l2_device *v4l2_dev;
+ struct fimc_m2m_device m2m;
+ struct fimc_vid_cap vid_cap;
+ unsigned long state;
+};
+
+/**
+ * struct fimc_ctrls - v4l2 controls structure
+ * @handler: the control handler
+ * @colorfx: image effect control
+ * @colorfx_cbcr: Cb/Cr coefficients control
+ * @rotate: image rotation control
+ * @hflip: horizontal flip control
+ * @vflip: vertical flip control
+ * @alpha: RGB alpha control
+ * @ready: true if @handler is initialized
+ */
+struct fimc_ctrls {
+ struct v4l2_ctrl_handler handler;
+ struct {
+ struct v4l2_ctrl *colorfx;
+ struct v4l2_ctrl *colorfx_cbcr;
+ };
+ struct v4l2_ctrl *rotate;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *alpha;
+ bool ready;
+};
+
+/**
+ * struct fimc_ctx - the device context data
+ * @s_frame: source frame properties
+ * @d_frame: destination frame properties
+ * @out_order_1p: output 1-plane YCBCR order
+ * @out_order_2p: output 2-plane YCBCR order
+ * @in_order_1p: input 1-plane YCBCR order
+ * @in_order_2p: input 2-plane YCBCR order
+ * @in_path: input mode (DMA or camera)
+ * @out_path: output mode (DMA or FIFO)
+ * @scaler: image scaler properties
+ * @effect: image effect
+ * @rotation: image clockwise rotation in degrees
+ * @hflip: indicates image horizontal flip if set
+ * @vflip: indicates image vertical flip if set
+ * @flags: additional flags for image conversion
+ * @state: flags to keep track of user configuration
+ * @fimc_dev: the FIMC device this context applies to
+ * @fh: v4l2 file handle
+ * @ctrls: v4l2 controls structure
+ */
+struct fimc_ctx {
+ struct fimc_frame s_frame;
+ struct fimc_frame d_frame;
+ u32 out_order_1p;
+ u32 out_order_2p;
+ u32 in_order_1p;
+ u32 in_order_2p;
+ enum fimc_datapath in_path;
+ enum fimc_datapath out_path;
+ struct fimc_scaler scaler;
+ struct fimc_effect effect;
+ int rotation;
+ unsigned int hflip:1;
+ unsigned int vflip:1;
+ u32 flags;
+ u32 state;
+ struct fimc_dev *fimc_dev;
+ struct v4l2_fh fh;
+ struct fimc_ctrls ctrls;
+};
+
+#define fh_to_ctx(__fh) container_of(__fh, struct fimc_ctx, fh)
+
+static inline void set_frame_bounds(struct fimc_frame *f, u32 width, u32 height)
+{
+ f->o_width = width;
+ f->o_height = height;
+ f->f_width = width;
+ f->f_height = height;
+}
+
+static inline void set_frame_crop(struct fimc_frame *f,
+ u32 left, u32 top, u32 width, u32 height)
+{
+ f->offs_h = left;
+ f->offs_v = top;
+ f->width = width;
+ f->height = height;
+}
+
+static inline u32 fimc_get_format_depth(struct fimc_fmt *ff)
+{
+ u32 i, depth = 0;
+
+ if (ff != NULL)
+ for (i = 0; i < ff->colplanes; i++)
+ depth += ff->depth[i];
+ return depth;
+}
+
+static inline bool fimc_capture_active(struct fimc_dev *fimc)
+{
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ ret = !!(fimc->state & (1 << ST_CAPT_RUN) ||
+ fimc->state & (1 << ST_CAPT_PEND));
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return ret;
+}
+
+static inline void fimc_ctx_state_set(u32 state, struct fimc_ctx *ctx)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->fimc_dev->slock, flags);
+ ctx->state |= state;
+ spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags);
+}
+
+static inline bool fimc_ctx_state_is_set(u32 mask, struct fimc_ctx *ctx)
+{
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&ctx->fimc_dev->slock, flags);
+ ret = (ctx->state & mask) == mask;
+ spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags);
+ return ret;
+}
+
+static inline int tiled_fmt(struct fimc_fmt *fmt)
+{
+ return fmt->fourcc == V4L2_PIX_FMT_NV12MT;
+}
+
+static inline bool fimc_jpeg_fourcc(u32 pixelformat)
+{
+ return (pixelformat == V4L2_PIX_FMT_JPEG ||
+ pixelformat == V4L2_PIX_FMT_S5C_UYVY_JPG);
+}
+
+static inline bool fimc_user_defined_mbus_fmt(u32 code)
+{
+ return (code == MEDIA_BUS_FMT_JPEG_1X8 ||
+ code == MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8);
+}
+
+/* Return the alpha component bit mask */
+static inline int fimc_get_alpha_mask(struct fimc_fmt *fmt)
+{
+ switch (fmt->color) {
+ case FIMC_FMT_RGB444: return 0x0f;
+ case FIMC_FMT_RGB555: return 0x01;
+ case FIMC_FMT_RGB888: return 0xff;
+ default: return 0;
+ };
+}
+
+static inline struct fimc_frame *ctx_get_frame(struct fimc_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ struct fimc_frame *frame;
+
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ||
+ type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ if (fimc_ctx_state_is_set(FIMC_CTX_M2M, ctx))
+ frame = &ctx->s_frame;
+ else
+ return ERR_PTR(-EINVAL);
+ } else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
+ type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ frame = &ctx->d_frame;
+ } else {
+ v4l2_err(ctx->fimc_dev->v4l2_dev,
+ "Wrong buffer/video queue type (%d)\n", type);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return frame;
+}
+
+/* -----------------------------------------------------*/
+/* fimc-core.c */
+int fimc_vidioc_enum_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f);
+int fimc_ctrls_create(struct fimc_ctx *ctx);
+void fimc_ctrls_delete(struct fimc_ctx *ctx);
+void fimc_ctrls_activate(struct fimc_ctx *ctx, bool active);
+void fimc_alpha_ctrl_update(struct fimc_ctx *ctx);
+void __fimc_get_format(struct fimc_frame *frame, struct v4l2_format *f);
+void fimc_adjust_mplane_format(struct fimc_fmt *fmt, u32 width, u32 height,
+ struct v4l2_pix_format_mplane *pix);
+struct fimc_fmt *fimc_find_format(const u32 *pixelformat, const u32 *mbus_code,
+ unsigned int mask, int index);
+struct fimc_fmt *fimc_get_format(unsigned int index);
+
+int fimc_check_scaler_ratio(struct fimc_ctx *ctx, int sw, int sh,
+ int dw, int dh, int rotation);
+int fimc_set_scaler_info(struct fimc_ctx *ctx);
+int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags);
+int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb,
+ struct fimc_frame *frame, struct fimc_addr *addr);
+void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f);
+void fimc_set_yuv_order(struct fimc_ctx *ctx);
+void fimc_capture_irq_handler(struct fimc_dev *fimc, int deq_buf);
+
+int fimc_register_m2m_device(struct fimc_dev *fimc,
+ struct v4l2_device *v4l2_dev);
+void fimc_unregister_m2m_device(struct fimc_dev *fimc);
+int fimc_register_driver(void);
+void fimc_unregister_driver(void);
+
+#ifdef CONFIG_MFD_SYSCON
+static inline struct regmap * fimc_get_sysreg_regmap(struct device_node *node)
+{
+ return syscon_regmap_lookup_by_phandle(node, "samsung,sysreg");
+}
+#else
+#define fimc_get_sysreg_regmap(node) (NULL)
+#endif
+
+/* -----------------------------------------------------*/
+/* fimc-m2m.c */
+void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state);
+
+/* -----------------------------------------------------*/
+/* fimc-capture.c */
+int fimc_initialize_capture_subdev(struct fimc_dev *fimc);
+void fimc_unregister_capture_subdev(struct fimc_dev *fimc);
+int fimc_capture_ctrls_create(struct fimc_dev *fimc);
+void fimc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification,
+ void *arg);
+int fimc_capture_suspend(struct fimc_dev *fimc);
+int fimc_capture_resume(struct fimc_dev *fimc);
+
+/*
+ * Buffer list manipulation functions. Must be called with fimc.slock held.
+ */
+
+/**
+ * fimc_active_queue_add - add buffer to the capture active buffers queue
+ * @vid_cap: camera capture device information
+ * @buf: buffer to add to the active buffers list
+ */
+static inline void fimc_active_queue_add(struct fimc_vid_cap *vid_cap,
+ struct fimc_vid_buffer *buf)
+{
+ list_add_tail(&buf->list, &vid_cap->active_buf_q);
+ vid_cap->active_buf_cnt++;
+}
+
+/**
+ * fimc_active_queue_pop - pop buffer from the capture active buffers queue
+ * @vid_cap: camera capture device information
+ *
+ * The caller must assure the active_buf_q list is not empty.
+ */
+static inline struct fimc_vid_buffer *fimc_active_queue_pop(
+ struct fimc_vid_cap *vid_cap)
+{
+ struct fimc_vid_buffer *buf;
+ buf = list_entry(vid_cap->active_buf_q.next,
+ struct fimc_vid_buffer, list);
+ list_del(&buf->list);
+ vid_cap->active_buf_cnt--;
+ return buf;
+}
+
+/**
+ * fimc_pending_queue_add - add buffer to the capture pending buffers queue
+ * @vid_cap: camera capture device information
+ * @buf: buffer to add to the pending buffers list
+ */
+static inline void fimc_pending_queue_add(struct fimc_vid_cap *vid_cap,
+ struct fimc_vid_buffer *buf)
+{
+ list_add_tail(&buf->list, &vid_cap->pending_buf_q);
+}
+
+/**
+ * fimc_pending_queue_pop - pop buffer from the capture pending buffers queue
+ * @vid_cap: camera capture device information
+ *
+ * The caller must assure the pending_buf_q list is not empty.
+ */
+static inline struct fimc_vid_buffer *fimc_pending_queue_pop(
+ struct fimc_vid_cap *vid_cap)
+{
+ struct fimc_vid_buffer *buf;
+ buf = list_entry(vid_cap->pending_buf_q.next,
+ struct fimc_vid_buffer, list);
+ list_del(&buf->list);
+ return buf;
+}
+
+#endif /* FIMC_CORE_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h
new file mode 100644
index 0000000000..87978609ad
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung Exynos4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * FIMC-IS command set definitions
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#ifndef FIMC_IS_CMD_H_
+#define FIMC_IS_CMD_H_
+
+#define FIMC_IS_COMMAND_VER 110 /* FIMC-IS command set version 1.10 */
+
+/* Enumeration of commands between the FIMC-IS and the host processor. */
+
+/* HOST to FIMC-IS */
+#define HIC_PREVIEW_STILL 0x0001
+#define HIC_PREVIEW_VIDEO 0x0002
+#define HIC_CAPTURE_STILL 0x0003
+#define HIC_CAPTURE_VIDEO 0x0004
+#define HIC_STREAM_ON 0x0005
+#define HIC_STREAM_OFF 0x0006
+#define HIC_SET_PARAMETER 0x0007
+#define HIC_GET_PARAMETER 0x0008
+#define HIC_SET_TUNE 0x0009
+#define HIC_GET_STATUS 0x000b
+/* Sensor part */
+#define HIC_OPEN_SENSOR 0x000c
+#define HIC_CLOSE_SENSOR 0x000d
+#define HIC_SIMMIAN_INIT 0x000e
+#define HIC_SIMMIAN_WRITE 0x000f
+#define HIC_SIMMIAN_READ 0x0010
+#define HIC_POWER_DOWN 0x0011
+#define HIC_GET_SET_FILE_ADDR 0x0012
+#define HIC_LOAD_SET_FILE 0x0013
+#define HIC_MSG_CONFIG 0x0014
+#define HIC_MSG_TEST 0x0015
+/* FIMC-IS to HOST */
+#define IHC_GET_SENSOR_NUM 0x1000
+#define IHC_SET_SHOT_MARK 0x1001
+/* parameter1: frame number */
+/* parameter2: confidence level (smile 0~100) */
+/* parameter3: confidence level (blink 0~100) */
+#define IHC_SET_FACE_MARK 0x1002
+/* parameter1: coordinate count */
+/* parameter2: coordinate buffer address */
+#define IHC_FRAME_DONE 0x1003
+/* parameter1: frame start number */
+/* parameter2: frame count */
+#define IHC_AA_DONE 0x1004
+#define IHC_NOT_READY 0x1005
+
+#define IH_REPLY_DONE 0x2000
+#define IH_REPLY_NOT_DONE 0x2001
+
+enum fimc_is_scenario {
+ IS_SC_PREVIEW_STILL,
+ IS_SC_PREVIEW_VIDEO,
+ IS_SC_CAPTURE_STILL,
+ IS_SC_CAPTURE_VIDEO,
+ IS_SC_MAX
+};
+
+enum fimc_is_sub_scenario {
+ IS_SC_SUB_DEFAULT,
+ IS_SC_SUB_PS_VTCALL,
+ IS_SC_SUB_CS_VTCALL,
+ IS_SC_SUB_PV_VTCALL,
+ IS_SC_SUB_CV_VTCALL,
+};
+
+struct is_common_regs {
+ u32 hicmd;
+ u32 hic_sensorid;
+ u32 hic_param[4];
+ u32 reserved1[4];
+
+ u32 ihcmd;
+ u32 ihc_sensorid;
+ u32 ihc_param[4];
+ u32 reserved2[4];
+
+ u32 isp_sensor_id;
+ u32 isp_param[2];
+ u32 reserved3[1];
+
+ u32 scc_sensor_id;
+ u32 scc_param[2];
+ u32 reserved4[1];
+
+ u32 dnr_sensor_id;
+ u32 dnr_param[2];
+ u32 reserved5[1];
+
+ u32 scp_sensor_id;
+ u32 scp_param[2];
+ u32 reserved6[29];
+} __packed;
+
+struct is_mcuctl_reg {
+ u32 mcuctl;
+ u32 bboar;
+
+ u32 intgr0;
+ u32 intcr0;
+ u32 intmr0;
+ u32 intsr0;
+ u32 intmsr0;
+
+ u32 intgr1;
+ u32 intcr1;
+ u32 intmr1;
+ u32 intsr1;
+ u32 intmsr1;
+
+ u32 intcr2;
+ u32 intmr2;
+ u32 intsr2;
+ u32 intmsr2;
+
+ u32 gpoctrl;
+ u32 cpoenctlr;
+ u32 gpictlr;
+
+ u32 reserved[0xd];
+
+ struct is_common_regs common;
+} __packed;
+
+#endif /* FIMC_IS_CMD_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c
new file mode 100644
index 0000000000..7a48fad1df
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung Exynos4 SoC series FIMC-IS slave interface driver
+ *
+ * Error log interface functions
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include "fimc-is-errno.h"
+
+const char *fimc_is_param_strerr(unsigned int error)
+{
+ switch (error) {
+ case ERROR_COMMON_CMD:
+ return "ERROR_COMMON_CMD: Invalid Command";
+ case ERROR_COMMON_PARAMETER:
+ return "ERROR_COMMON_PARAMETER: Invalid Parameter";
+ case ERROR_COMMON_SETFILE_LOAD:
+ return "ERROR_COMMON_SETFILE_LOAD: Illegal Setfile Loading";
+ case ERROR_COMMON_SETFILE_ADJUST:
+ return "ERROR_COMMON_SETFILE_ADJUST: Setfile isn't adjusted";
+ case ERROR_COMMON_SETFILE_INDEX:
+ return "ERROR_COMMON_SETFILE_INDEX: Invalid setfile index";
+ case ERROR_COMMON_INPUT_PATH:
+ return "ERROR_COMMON_INPUT_PATH: Input path can be changed in ready state";
+ case ERROR_COMMON_INPUT_INIT:
+ return "ERROR_COMMON_INPUT_INIT: IP can not start if input path is not set";
+ case ERROR_COMMON_OUTPUT_PATH:
+ return "ERROR_COMMON_OUTPUT_PATH: Output path can be changed in ready state (stop)";
+ case ERROR_COMMON_OUTPUT_INIT:
+ return "ERROR_COMMON_OUTPUT_INIT: IP can not start if output path is not set";
+ case ERROR_CONTROL_BYPASS:
+ return "ERROR_CONTROL_BYPASS";
+ case ERROR_OTF_INPUT_FORMAT:
+ return "ERROR_OTF_INPUT_FORMAT: Invalid format (DRC: YUV444, FD: YUV444, 422, 420)";
+ case ERROR_OTF_INPUT_WIDTH:
+ return "ERROR_OTF_INPUT_WIDTH: Invalid width (DRC: 128~8192, FD: 32~8190)";
+ case ERROR_OTF_INPUT_HEIGHT:
+ return "ERROR_OTF_INPUT_HEIGHT: Invalid bit-width (DRC: 8~12bits, FD: 8bit)";
+ case ERROR_OTF_INPUT_BIT_WIDTH:
+ return "ERROR_OTF_INPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)";
+ case ERROR_DMA_INPUT_WIDTH:
+ return "ERROR_DMA_INPUT_WIDTH: Invalid width (DRC: 128~8192, FD: 32~8190)";
+ case ERROR_DMA_INPUT_HEIGHT:
+ return "ERROR_DMA_INPUT_HEIGHT: Invalid height (DRC: 64~8192, FD: 16~8190)";
+ case ERROR_DMA_INPUT_FORMAT:
+ return "ERROR_DMA_INPUT_FORMAT: Invalid format (DRC: YUV444 or YUV422, FD: YUV444,422,420)";
+ case ERROR_DMA_INPUT_BIT_WIDTH:
+ return "ERROR_DMA_INPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)";
+ case ERROR_DMA_INPUT_ORDER:
+ return "ERROR_DMA_INPUT_ORDER: Invalid order(DRC: YYCbCr,YCbYCr,FD:NO,YYCbCr,YCbYCr,CbCr,CrCb)";
+ case ERROR_DMA_INPUT_PLANE:
+ return "ERROR_DMA_INPUT_PLANE: Invalid plane (DRC: 3, FD: 1, 2, 3)";
+ case ERROR_OTF_OUTPUT_WIDTH:
+ return "ERROR_OTF_OUTPUT_WIDTH: Invalid width (DRC: 128~8192)";
+ case ERROR_OTF_OUTPUT_HEIGHT:
+ return "ERROR_OTF_OUTPUT_HEIGHT: Invalid height (DRC: 64~8192)";
+ case ERROR_OTF_OUTPUT_FORMAT:
+ return "ERROR_OTF_OUTPUT_FORMAT: Invalid format (DRC: YUV444)";
+ case ERROR_OTF_OUTPUT_BIT_WIDTH:
+ return "ERROR_OTF_OUTPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)";
+ case ERROR_DMA_OUTPUT_WIDTH:
+ return "ERROR_DMA_OUTPUT_WIDTH";
+ case ERROR_DMA_OUTPUT_HEIGHT:
+ return "ERROR_DMA_OUTPUT_HEIGHT";
+ case ERROR_DMA_OUTPUT_FORMAT:
+ return "ERROR_DMA_OUTPUT_FORMAT";
+ case ERROR_DMA_OUTPUT_BIT_WIDTH:
+ return "ERROR_DMA_OUTPUT_BIT_WIDTH";
+ case ERROR_DMA_OUTPUT_PLANE:
+ return "ERROR_DMA_OUTPUT_PLANE";
+ case ERROR_DMA_OUTPUT_ORDER:
+ return "ERROR_DMA_OUTPUT_ORDER";
+
+ /* Sensor Error(100~199) */
+ case ERROR_SENSOR_I2C_FAIL:
+ return "ERROR_SENSOR_I2C_FAIL";
+ case ERROR_SENSOR_INVALID_FRAMERATE:
+ return "ERROR_SENSOR_INVALID_FRAMERATE";
+ case ERROR_SENSOR_INVALID_EXPOSURETIME:
+ return "ERROR_SENSOR_INVALID_EXPOSURETIME";
+ case ERROR_SENSOR_INVALID_SIZE:
+ return "ERROR_SENSOR_INVALID_SIZE";
+ case ERROR_SENSOR_INVALID_SETTING:
+ return "ERROR_SENSOR_INVALID_SETTING";
+ case ERROR_SENSOR_ACTUATOR_INIT_FAIL:
+ return "ERROR_SENSOR_ACTUATOR_INIT_FAIL";
+ case ERROR_SENSOR_INVALID_AF_POS:
+ return "ERROR_SENSOR_INVALID_AF_POS";
+ case ERROR_SENSOR_UNSUPPORT_FUNC:
+ return "ERROR_SENSOR_UNSUPPORT_FUNC";
+ case ERROR_SENSOR_UNSUPPORT_PERI:
+ return "ERROR_SENSOR_UNSUPPORT_PERI";
+ case ERROR_SENSOR_UNSUPPORT_AF:
+ return "ERROR_SENSOR_UNSUPPORT_AF";
+
+ /* ISP Error (200~299) */
+ case ERROR_ISP_AF_BUSY:
+ return "ERROR_ISP_AF_BUSY";
+ case ERROR_ISP_AF_INVALID_COMMAND:
+ return "ERROR_ISP_AF_INVALID_COMMAND";
+ case ERROR_ISP_AF_INVALID_MODE:
+ return "ERROR_ISP_AF_INVALID_MODE";
+
+ /* DRC Error (300~399) */
+ /* FD Error (400~499) */
+ case ERROR_FD_CONFIG_MAX_NUMBER_STATE:
+ return "ERROR_FD_CONFIG_MAX_NUMBER_STATE";
+ case ERROR_FD_CONFIG_MAX_NUMBER_INVALID:
+ return "ERROR_FD_CONFIG_MAX_NUMBER_INVALID";
+ case ERROR_FD_CONFIG_YAW_ANGLE_STATE:
+ return "ERROR_FD_CONFIG_YAW_ANGLE_STATE";
+ case ERROR_FD_CONFIG_YAW_ANGLE_INVALID:
+ return "ERROR_FD_CONFIG_YAW_ANGLE_INVALID\n";
+ case ERROR_FD_CONFIG_ROLL_ANGLE_STATE:
+ return "ERROR_FD_CONFIG_ROLL_ANGLE_STATE";
+ case ERROR_FD_CONFIG_ROLL_ANGLE_INVALID:
+ return "ERROR_FD_CONFIG_ROLL_ANGLE_INVALID";
+ case ERROR_FD_CONFIG_SMILE_MODE_INVALID:
+ return "ERROR_FD_CONFIG_SMILE_MODE_INVALID";
+ case ERROR_FD_CONFIG_BLINK_MODE_INVALID:
+ return "ERROR_FD_CONFIG_BLINK_MODE_INVALID";
+ case ERROR_FD_CONFIG_EYES_DETECT_INVALID:
+ return "ERROR_FD_CONFIG_EYES_DETECT_INVALID";
+ case ERROR_FD_CONFIG_MOUTH_DETECT_INVALID:
+ return "ERROR_FD_CONFIG_MOUTH_DETECT_INVALID";
+ case ERROR_FD_CONFIG_ORIENTATION_STATE:
+ return "ERROR_FD_CONFIG_ORIENTATION_STATE";
+ case ERROR_FD_CONFIG_ORIENTATION_INVALID:
+ return "ERROR_FD_CONFIG_ORIENTATION_INVALID";
+ case ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID:
+ return "ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID";
+ case ERROR_FD_RESULT:
+ return "ERROR_FD_RESULT";
+ case ERROR_FD_MODE:
+ return "ERROR_FD_MODE";
+ default:
+ return "Unknown";
+ }
+}
+
+const char *fimc_is_strerr(unsigned int error)
+{
+ error &= ~IS_ERROR_TIME_OUT_FLAG;
+
+ switch (error) {
+ /* General */
+ case IS_ERROR_INVALID_COMMAND:
+ return "IS_ERROR_INVALID_COMMAND";
+ case IS_ERROR_REQUEST_FAIL:
+ return "IS_ERROR_REQUEST_FAIL";
+ case IS_ERROR_INVALID_SCENARIO:
+ return "IS_ERROR_INVALID_SCENARIO";
+ case IS_ERROR_INVALID_SENSORID:
+ return "IS_ERROR_INVALID_SENSORID";
+ case IS_ERROR_INVALID_MODE_CHANGE:
+ return "IS_ERROR_INVALID_MODE_CHANGE";
+ case IS_ERROR_INVALID_MAGIC_NUMBER:
+ return "IS_ERROR_INVALID_MAGIC_NUMBER";
+ case IS_ERROR_INVALID_SETFILE_HDR:
+ return "IS_ERROR_INVALID_SETFILE_HDR";
+ case IS_ERROR_BUSY:
+ return "IS_ERROR_BUSY";
+ case IS_ERROR_SET_PARAMETER:
+ return "IS_ERROR_SET_PARAMETER";
+ case IS_ERROR_INVALID_PATH:
+ return "IS_ERROR_INVALID_PATH";
+ case IS_ERROR_OPEN_SENSOR_FAIL:
+ return "IS_ERROR_OPEN_SENSOR_FAIL";
+ case IS_ERROR_ENTRY_MSG_THREAD_DOWN:
+ return "IS_ERROR_ENTRY_MSG_THREAD_DOWN";
+ case IS_ERROR_ISP_FRAME_END_NOT_DONE:
+ return "IS_ERROR_ISP_FRAME_END_NOT_DONE";
+ case IS_ERROR_DRC_FRAME_END_NOT_DONE:
+ return "IS_ERROR_DRC_FRAME_END_NOT_DONE";
+ case IS_ERROR_SCALERC_FRAME_END_NOT_DONE:
+ return "IS_ERROR_SCALERC_FRAME_END_NOT_DONE";
+ case IS_ERROR_ODC_FRAME_END_NOT_DONE:
+ return "IS_ERROR_ODC_FRAME_END_NOT_DONE";
+ case IS_ERROR_DIS_FRAME_END_NOT_DONE:
+ return "IS_ERROR_DIS_FRAME_END_NOT_DONE";
+ case IS_ERROR_TDNR_FRAME_END_NOT_DONE:
+ return "IS_ERROR_TDNR_FRAME_END_NOT_DONE";
+ case IS_ERROR_SCALERP_FRAME_END_NOT_DONE:
+ return "IS_ERROR_SCALERP_FRAME_END_NOT_DONE";
+ case IS_ERROR_WAIT_STREAM_OFF_NOT_DONE:
+ return "IS_ERROR_WAIT_STREAM_OFF_NOT_DONE";
+ case IS_ERROR_NO_MSG_IS_RECEIVED:
+ return "IS_ERROR_NO_MSG_IS_RECEIVED";
+ case IS_ERROR_SENSOR_MSG_FAIL:
+ return "IS_ERROR_SENSOR_MSG_FAIL";
+ case IS_ERROR_ISP_MSG_FAIL:
+ return "IS_ERROR_ISP_MSG_FAIL";
+ case IS_ERROR_DRC_MSG_FAIL:
+ return "IS_ERROR_DRC_MSG_FAIL";
+ case IS_ERROR_LHFD_MSG_FAIL:
+ return "IS_ERROR_LHFD_MSG_FAIL";
+ case IS_ERROR_UNKNOWN:
+ return "IS_ERROR_UNKNOWN";
+
+ /* Sensor */
+ case IS_ERROR_SENSOR_PWRDN_FAIL:
+ return "IS_ERROR_SENSOR_PWRDN_FAIL";
+
+ /* ISP */
+ case IS_ERROR_ISP_PWRDN_FAIL:
+ return "IS_ERROR_ISP_PWRDN_FAIL";
+ case IS_ERROR_ISP_MULTIPLE_INPUT:
+ return "IS_ERROR_ISP_MULTIPLE_INPUT";
+ case IS_ERROR_ISP_ABSENT_INPUT:
+ return "IS_ERROR_ISP_ABSENT_INPUT";
+ case IS_ERROR_ISP_ABSENT_OUTPUT:
+ return "IS_ERROR_ISP_ABSENT_OUTPUT";
+ case IS_ERROR_ISP_NONADJACENT_OUTPUT:
+ return "IS_ERROR_ISP_NONADJACENT_OUTPUT";
+ case IS_ERROR_ISP_FORMAT_MISMATCH:
+ return "IS_ERROR_ISP_FORMAT_MISMATCH";
+ case IS_ERROR_ISP_WIDTH_MISMATCH:
+ return "IS_ERROR_ISP_WIDTH_MISMATCH";
+ case IS_ERROR_ISP_HEIGHT_MISMATCH:
+ return "IS_ERROR_ISP_HEIGHT_MISMATCH";
+ case IS_ERROR_ISP_BITWIDTH_MISMATCH:
+ return "IS_ERROR_ISP_BITWIDTH_MISMATCH";
+ case IS_ERROR_ISP_FRAME_END_TIME_OUT:
+ return "IS_ERROR_ISP_FRAME_END_TIME_OUT";
+
+ /* DRC */
+ case IS_ERROR_DRC_PWRDN_FAIL:
+ return "IS_ERROR_DRC_PWRDN_FAIL";
+ case IS_ERROR_DRC_MULTIPLE_INPUT:
+ return "IS_ERROR_DRC_MULTIPLE_INPUT";
+ case IS_ERROR_DRC_ABSENT_INPUT:
+ return "IS_ERROR_DRC_ABSENT_INPUT";
+ case IS_ERROR_DRC_NONADJACENT_INPUT:
+ return "IS_ERROR_DRC_NONADJACENT_INPUT";
+ case IS_ERROR_DRC_ABSENT_OUTPUT:
+ return "IS_ERROR_DRC_ABSENT_OUTPUT";
+ case IS_ERROR_DRC_NONADJACENT_OUTPUT:
+ return "IS_ERROR_DRC_NONADJACENT_OUTPUT";
+ case IS_ERROR_DRC_FORMAT_MISMATCH:
+ return "IS_ERROR_DRC_FORMAT_MISMATCH";
+ case IS_ERROR_DRC_WIDTH_MISMATCH:
+ return "IS_ERROR_DRC_WIDTH_MISMATCH";
+ case IS_ERROR_DRC_HEIGHT_MISMATCH:
+ return "IS_ERROR_DRC_HEIGHT_MISMATCH";
+ case IS_ERROR_DRC_BITWIDTH_MISMATCH:
+ return "IS_ERROR_DRC_BITWIDTH_MISMATCH";
+ case IS_ERROR_DRC_FRAME_END_TIME_OUT:
+ return "IS_ERROR_DRC_FRAME_END_TIME_OUT";
+
+ /* FD */
+ case IS_ERROR_FD_PWRDN_FAIL:
+ return "IS_ERROR_FD_PWRDN_FAIL";
+ case IS_ERROR_FD_MULTIPLE_INPUT:
+ return "IS_ERROR_FD_MULTIPLE_INPUT";
+ case IS_ERROR_FD_ABSENT_INPUT:
+ return "IS_ERROR_FD_ABSENT_INPUT";
+ case IS_ERROR_FD_NONADJACENT_INPUT:
+ return "IS_ERROR_FD_NONADJACENT_INPUT";
+ case IS_ERROR_LHFD_FRAME_END_TIME_OUT:
+ return "IS_ERROR_LHFD_FRAME_END_TIME_OUT";
+ default:
+ return "Unknown";
+ }
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h
new file mode 100644
index 0000000000..809e117331
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung Exynos4 SoC series FIMC-IS slave interface driver
+ *
+ * FIMC-IS error code definition
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+*/
+
+#ifndef FIMC_IS_ERR_H_
+#define FIMC_IS_ERR_H_
+
+#define IS_ERROR_VER 011 /* IS ERROR VERSION 0.11 */
+
+enum {
+ IS_ERROR_NONE,
+
+ /* General 1 ~ 99 */
+ IS_ERROR_INVALID_COMMAND,
+ IS_ERROR_REQUEST_FAIL,
+ IS_ERROR_INVALID_SCENARIO,
+ IS_ERROR_INVALID_SENSORID,
+ IS_ERROR_INVALID_MODE_CHANGE,
+ IS_ERROR_INVALID_MAGIC_NUMBER,
+ IS_ERROR_INVALID_SETFILE_HDR,
+ IS_ERROR_BUSY,
+ IS_ERROR_SET_PARAMETER,
+ IS_ERROR_INVALID_PATH,
+ IS_ERROR_OPEN_SENSOR_FAIL,
+ IS_ERROR_ENTRY_MSG_THREAD_DOWN,
+ IS_ERROR_ISP_FRAME_END_NOT_DONE,
+ IS_ERROR_DRC_FRAME_END_NOT_DONE,
+ IS_ERROR_SCALERC_FRAME_END_NOT_DONE,
+ IS_ERROR_ODC_FRAME_END_NOT_DONE,
+ IS_ERROR_DIS_FRAME_END_NOT_DONE,
+ IS_ERROR_TDNR_FRAME_END_NOT_DONE,
+ IS_ERROR_SCALERP_FRAME_END_NOT_DONE,
+ IS_ERROR_WAIT_STREAM_OFF_NOT_DONE,
+ IS_ERROR_NO_MSG_IS_RECEIVED,
+ IS_ERROR_SENSOR_MSG_FAIL,
+ IS_ERROR_ISP_MSG_FAIL,
+ IS_ERROR_DRC_MSG_FAIL,
+ IS_ERROR_SCALERC_MSG_FAIL,
+ IS_ERROR_ODC_MSG_FAIL,
+ IS_ERROR_DIS_MSG_FAIL,
+ IS_ERROR_TDNR_MSG_FAIL,
+ IS_ERROR_SCALERP_MSG_FAIL,
+ IS_ERROR_LHFD_MSG_FAIL,
+ IS_ERROR_LHFD_INTERNAL_STOP,
+
+ /* Sensor 100 ~ 199 */
+ IS_ERROR_SENSOR_PWRDN_FAIL = 100,
+ IS_ERROR_SENSOR_STREAM_ON_FAIL,
+ IS_ERROR_SENSOR_STREAM_OFF_FAIL,
+
+ /* ISP 200 ~ 299 */
+ IS_ERROR_ISP_PWRDN_FAIL = 200,
+ IS_ERROR_ISP_MULTIPLE_INPUT,
+ IS_ERROR_ISP_ABSENT_INPUT,
+ IS_ERROR_ISP_ABSENT_OUTPUT,
+ IS_ERROR_ISP_NONADJACENT_OUTPUT,
+ IS_ERROR_ISP_FORMAT_MISMATCH,
+ IS_ERROR_ISP_WIDTH_MISMATCH,
+ IS_ERROR_ISP_HEIGHT_MISMATCH,
+ IS_ERROR_ISP_BITWIDTH_MISMATCH,
+ IS_ERROR_ISP_FRAME_END_TIME_OUT,
+
+ /* DRC 300 ~ 399 */
+ IS_ERROR_DRC_PWRDN_FAIL = 300,
+ IS_ERROR_DRC_MULTIPLE_INPUT,
+ IS_ERROR_DRC_ABSENT_INPUT,
+ IS_ERROR_DRC_NONADJACENT_INPUT,
+ IS_ERROR_DRC_ABSENT_OUTPUT,
+ IS_ERROR_DRC_NONADJACENT_OUTPUT,
+ IS_ERROR_DRC_FORMAT_MISMATCH,
+ IS_ERROR_DRC_WIDTH_MISMATCH,
+ IS_ERROR_DRC_HEIGHT_MISMATCH,
+ IS_ERROR_DRC_BITWIDTH_MISMATCH,
+ IS_ERROR_DRC_FRAME_END_TIME_OUT,
+
+ /* SCALERC 400 ~ 499 */
+ IS_ERROR_SCALERC_PWRDN_FAIL = 400,
+
+ /* ODC 500 ~ 599 */
+ IS_ERROR_ODC_PWRDN_FAIL = 500,
+
+ /* DIS 600 ~ 699 */
+ IS_ERROR_DIS_PWRDN_FAIL = 600,
+
+ /* TDNR 700 ~ 799 */
+ IS_ERROR_TDNR_PWRDN_FAIL = 700,
+
+ /* SCALERC 800 ~ 899 */
+ IS_ERROR_SCALERP_PWRDN_FAIL = 800,
+
+ /* FD 900 ~ 999 */
+ IS_ERROR_FD_PWRDN_FAIL = 900,
+ IS_ERROR_FD_MULTIPLE_INPUT,
+ IS_ERROR_FD_ABSENT_INPUT,
+ IS_ERROR_FD_NONADJACENT_INPUT,
+ IS_ERROR_LHFD_FRAME_END_TIME_OUT,
+
+ IS_ERROR_UNKNOWN = 1000,
+};
+
+#define IS_ERROR_TIME_OUT_FLAG 0x80000000
+
+/* Set parameter error enum */
+enum fimc_is_error {
+ /* Common error (0~99) */
+ ERROR_COMMON_NONE = 0,
+ ERROR_COMMON_CMD = 1, /* Invalid command */
+ ERROR_COMMON_PARAMETER = 2, /* Invalid parameter */
+ /* setfile is not loaded before adjusting */
+ ERROR_COMMON_SETFILE_LOAD = 3,
+ /* setfile is not Adjusted before running. */
+ ERROR_COMMON_SETFILE_ADJUST = 4,
+ /* Index of setfile is not valid (0~MAX_SETFILE_NUM-1) */
+ ERROR_COMMON_SETFILE_INDEX = 5,
+ /* Input path can be changed in ready state(stop) */
+ ERROR_COMMON_INPUT_PATH = 6,
+ /* IP can not start if input path is not set */
+ ERROR_COMMON_INPUT_INIT = 7,
+ /* Output path can be changed in ready state (stop) */
+ ERROR_COMMON_OUTPUT_PATH = 8,
+ /* IP can not start if output path is not set */
+ ERROR_COMMON_OUTPUT_INIT = 9,
+
+ ERROR_CONTROL_NONE = ERROR_COMMON_NONE,
+ ERROR_CONTROL_BYPASS = 11, /* Enable or Disable */
+
+ ERROR_OTF_INPUT_NONE = ERROR_COMMON_NONE,
+ ERROR_OTF_INPUT_CMD = 21,
+ /* invalid format (DRC: YUV444, FD: YUV444, 422, 420) */
+ ERROR_OTF_INPUT_FORMAT = 22,
+ /* invalid width (DRC: 128~8192, FD: 32~8190) */
+ ERROR_OTF_INPUT_WIDTH = 23,
+ /* invalid height (DRC: 64~8192, FD: 16~8190) */
+ ERROR_OTF_INPUT_HEIGHT = 24,
+ /* invalid bit-width (DRC: 8~12bits, FD: 8bit) */
+ ERROR_OTF_INPUT_BIT_WIDTH = 25,
+ /* invalid FrameTime for ISP */
+ ERROR_OTF_INPUT_USER_FRAMETIIME = 26,
+
+ ERROR_DMA_INPUT_NONE = ERROR_COMMON_NONE,
+ /* invalid width (DRC: 128~8192, FD: 32~8190) */
+ ERROR_DMA_INPUT_WIDTH = 31,
+ /* invalid height (DRC: 64~8192, FD: 16~8190) */
+ ERROR_DMA_INPUT_HEIGHT = 32,
+ /* invalid format (DRC: YUV444 or YUV422, FD: YUV444, 422, 420) */
+ ERROR_DMA_INPUT_FORMAT = 33,
+ /* invalid bit-width (DRC: 8~12bit, FD: 8bit) */
+ ERROR_DMA_INPUT_BIT_WIDTH = 34,
+ /* invalid order(DRC: YYCbCrorYCbYCr, FD:NO,YYCbCr,YCbYCr,CbCr,CrCb) */
+ ERROR_DMA_INPUT_ORDER = 35,
+ /* invalid plane (DRC: 3, FD: 1, 2, 3) */
+ ERROR_DMA_INPUT_PLANE = 36,
+
+ ERROR_OTF_OUTPUT_NONE = ERROR_COMMON_NONE,
+ /* invalid width (DRC: 128~8192) */
+ ERROR_OTF_OUTPUT_WIDTH = 41,
+ /* invalid height (DRC: 64~8192) */
+ ERROR_OTF_OUTPUT_HEIGHT = 42,
+ /* invalid format (DRC: YUV444) */
+ ERROR_OTF_OUTPUT_FORMAT = 43,
+ /* invalid bit-width (DRC: 8~12bits) */
+ ERROR_OTF_OUTPUT_BIT_WIDTH = 44,
+
+ ERROR_DMA_OUTPUT_NONE = ERROR_COMMON_NONE,
+ ERROR_DMA_OUTPUT_WIDTH = 51, /* invalid width */
+ ERROR_DMA_OUTPUT_HEIGHT = 52, /* invalid height */
+ ERROR_DMA_OUTPUT_FORMAT = 53, /* invalid format */
+ ERROR_DMA_OUTPUT_BIT_WIDTH = 54, /* invalid bit-width */
+ ERROR_DMA_OUTPUT_PLANE = 55, /* invalid plane */
+ ERROR_DMA_OUTPUT_ORDER = 56, /* invalid order */
+
+ ERROR_GLOBAL_SHOTMODE_NONE = ERROR_COMMON_NONE,
+
+ /* SENSOR Error(100~199) */
+ ERROR_SENSOR_NONE = ERROR_COMMON_NONE,
+ ERROR_SENSOR_I2C_FAIL = 101,
+ ERROR_SENSOR_INVALID_FRAMERATE,
+ ERROR_SENSOR_INVALID_EXPOSURETIME,
+ ERROR_SENSOR_INVALID_SIZE,
+ ERROR_SENSOR_INVALID_SETTING,
+ ERROR_SENSOR_ACTUATOR_INIT_FAIL,
+ ERROR_SENSOR_INVALID_AF_POS,
+ ERROR_SENSOR_UNSUPPORT_FUNC,
+ ERROR_SENSOR_UNSUPPORT_PERI,
+ ERROR_SENSOR_UNSUPPORT_AF,
+
+ /* ISP Error (200~299) */
+ ERROR_ISP_AF_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_AF_BUSY = 201,
+ ERROR_ISP_AF_INVALID_COMMAND = 202,
+ ERROR_ISP_AF_INVALID_MODE = 203,
+ ERROR_ISP_FLASH_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_AWB_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_IMAGE_EFFECT_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_ISO_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_ADJUST_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_METERING_NONE = ERROR_COMMON_NONE,
+ ERROR_ISP_AFC_NONE = ERROR_COMMON_NONE,
+
+ /* DRC Error (300~399) */
+
+ /* FD Error (400~499) */
+ ERROR_FD_NONE = ERROR_COMMON_NONE,
+ /* Invalid max number (1~16) */
+ ERROR_FD_CONFIG_MAX_NUMBER_STATE = 401,
+ ERROR_FD_CONFIG_MAX_NUMBER_INVALID = 402,
+ ERROR_FD_CONFIG_YAW_ANGLE_STATE = 403,
+ ERROR_FD_CONFIG_YAW_ANGLE_INVALID = 404,
+ ERROR_FD_CONFIG_ROLL_ANGLE_STATE = 405,
+ ERROR_FD_CONFIG_ROLL_ANGLE_INVALID = 406,
+ ERROR_FD_CONFIG_SMILE_MODE_INVALID = 407,
+ ERROR_FD_CONFIG_BLINK_MODE_INVALID = 408,
+ ERROR_FD_CONFIG_EYES_DETECT_INVALID = 409,
+ ERROR_FD_CONFIG_MOUTH_DETECT_INVALID = 410,
+ ERROR_FD_CONFIG_ORIENTATION_STATE = 411,
+ ERROR_FD_CONFIG_ORIENTATION_INVALID = 412,
+ ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID = 413,
+ /* PARAM_FdResultStr can be only applied in ready-state or stream off */
+ ERROR_FD_RESULT = 414,
+ /* PARAM_FdModeStr can be only applied in ready-state or stream off */
+ ERROR_FD_MODE = 415,
+ /* Scaler Error (500 ~ 599) */
+ ERROR_SCALER_NO_NONE = ERROR_COMMON_NONE,
+ ERROR_SCALER_DMA_OUTSEL = 501,
+ ERROR_SCALER_H_RATIO = 502,
+ ERROR_SCALER_V_RATIO = 503,
+
+ ERROR_SCALER_IMAGE_EFFECT = 510,
+
+ ERROR_SCALER_ROTATE = 520,
+ ERROR_SCALER_FLIP = 521,
+};
+
+const char *fimc_is_strerr(unsigned int error);
+const char *fimc_is_param_strerr(unsigned int error);
+
+#endif /* FIMC_IS_ERR_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c
new file mode 100644
index 0000000000..bef6e9b4a2
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include "fimc-is-i2c.h"
+
+struct fimc_is_i2c {
+ struct i2c_adapter adapter;
+ struct clk *clock;
+};
+
+/*
+ * An empty algorithm is used as the actual I2C bus controller driver
+ * is implemented in the FIMC-IS subsystem firmware and the host CPU
+ * doesn't access the I2C bus controller.
+ */
+static u32 is_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm fimc_is_i2c_algorithm = {
+ .functionality = is_i2c_func,
+};
+
+static int fimc_is_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct fimc_is_i2c *isp_i2c;
+ struct i2c_adapter *i2c_adap;
+ int ret;
+
+ isp_i2c = devm_kzalloc(&pdev->dev, sizeof(*isp_i2c), GFP_KERNEL);
+ if (!isp_i2c)
+ return -ENOMEM;
+
+ isp_i2c->clock = devm_clk_get(&pdev->dev, "i2c_isp");
+ if (IS_ERR(isp_i2c->clock)) {
+ dev_err(&pdev->dev, "failed to get the clock\n");
+ return PTR_ERR(isp_i2c->clock);
+ }
+
+ i2c_adap = &isp_i2c->adapter;
+ i2c_adap->dev.of_node = node;
+ i2c_adap->dev.parent = &pdev->dev;
+ strscpy(i2c_adap->name, "exynos4x12-isp-i2c", sizeof(i2c_adap->name));
+ i2c_adap->owner = THIS_MODULE;
+ i2c_adap->algo = &fimc_is_i2c_algorithm;
+ i2c_adap->class = I2C_CLASS_SPD;
+
+ platform_set_drvdata(pdev, isp_i2c);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = i2c_add_adapter(i2c_adap);
+ if (ret < 0)
+ goto err_pm_dis;
+ /*
+ * Client drivers of this adapter don't do any I2C transfers as that
+ * is handled by the ISP firmware. But we rely on the runtime PM
+ * state propagation from the clients up to the adapter driver so
+ * clear the ignore_children flags here. PM rutnime calls are not
+ * used in probe() handler of clients of this adapter so there is
+ * no issues with clearing the flag right after registering the I2C
+ * adapter.
+ */
+ pm_suspend_ignore_children(&i2c_adap->dev, false);
+ return 0;
+
+err_pm_dis:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static void fimc_is_i2c_remove(struct platform_device *pdev)
+{
+ struct fimc_is_i2c *isp_i2c = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+ i2c_del_adapter(&isp_i2c->adapter);
+}
+
+#ifdef CONFIG_PM
+static int fimc_is_i2c_runtime_suspend(struct device *dev)
+{
+ struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(isp_i2c->clock);
+ return 0;
+}
+
+static int fimc_is_i2c_runtime_resume(struct device *dev)
+{
+ struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(isp_i2c->clock);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int fimc_is_i2c_suspend(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return fimc_is_i2c_runtime_suspend(dev);
+}
+
+static int fimc_is_i2c_resume(struct device *dev)
+{
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return fimc_is_i2c_runtime_resume(dev);
+}
+#endif
+
+static const struct dev_pm_ops fimc_is_i2c_pm_ops = {
+ SET_RUNTIME_PM_OPS(fimc_is_i2c_runtime_suspend,
+ fimc_is_i2c_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fimc_is_i2c_suspend, fimc_is_i2c_resume)
+};
+
+static const struct of_device_id fimc_is_i2c_of_match[] = {
+ { .compatible = FIMC_IS_I2C_COMPATIBLE },
+ { },
+};
+
+static struct platform_driver fimc_is_i2c_driver = {
+ .probe = fimc_is_i2c_probe,
+ .remove_new = fimc_is_i2c_remove,
+ .driver = {
+ .of_match_table = fimc_is_i2c_of_match,
+ .name = "fimc-isp-i2c",
+ .pm = &fimc_is_i2c_pm_ops,
+ }
+};
+
+int fimc_is_register_i2c_driver(void)
+{
+ return platform_driver_register(&fimc_is_i2c_driver);
+}
+
+void fimc_is_unregister_i2c_driver(void)
+{
+ platform_driver_unregister(&fimc_is_i2c_driver);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h
new file mode 100644
index 0000000000..a23bd20be6
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#define FIMC_IS_I2C_COMPATIBLE "samsung,exynos4212-i2c-isp"
+
+int fimc_is_register_i2c_driver(void);
+void fimc_is_unregister_i2c_driver(void);
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c
new file mode 100644
index 0000000000..9c816ae3b3
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c
@@ -0,0 +1,893 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+
+#include "fimc-is.h"
+#include "fimc-is-command.h"
+#include "fimc-is-errno.h"
+#include "fimc-is-param.h"
+#include "fimc-is-regs.h"
+#include "fimc-is-sensor.h"
+
+static void __hw_param_copy(void *dst, void *src)
+{
+ memcpy(dst, src, FIMC_IS_PARAM_MAX_SIZE);
+}
+
+static void __fimc_is_hw_update_param_global_shotmode(struct fimc_is *is)
+{
+ struct param_global_shotmode *dst, *src;
+
+ dst = &is->is_p_region->parameter.global.shotmode;
+ src = &is->config[is->config_index].global.shotmode;
+ __hw_param_copy(dst, src);
+}
+
+static void __fimc_is_hw_update_param_sensor_framerate(struct fimc_is *is)
+{
+ struct param_sensor_framerate *dst, *src;
+
+ dst = &is->is_p_region->parameter.sensor.frame_rate;
+ src = &is->config[is->config_index].sensor.frame_rate;
+ __hw_param_copy(dst, src);
+}
+
+int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset)
+{
+ struct is_param_region *par = &is->is_p_region->parameter;
+ struct chain_config *cfg = &is->config[is->config_index];
+
+ switch (offset) {
+ case PARAM_ISP_CONTROL:
+ __hw_param_copy(&par->isp.control, &cfg->isp.control);
+ break;
+
+ case PARAM_ISP_OTF_INPUT:
+ __hw_param_copy(&par->isp.otf_input, &cfg->isp.otf_input);
+ break;
+
+ case PARAM_ISP_DMA1_INPUT:
+ __hw_param_copy(&par->isp.dma1_input, &cfg->isp.dma1_input);
+ break;
+
+ case PARAM_ISP_DMA2_INPUT:
+ __hw_param_copy(&par->isp.dma2_input, &cfg->isp.dma2_input);
+ break;
+
+ case PARAM_ISP_AA:
+ __hw_param_copy(&par->isp.aa, &cfg->isp.aa);
+ break;
+
+ case PARAM_ISP_FLASH:
+ __hw_param_copy(&par->isp.flash, &cfg->isp.flash);
+ break;
+
+ case PARAM_ISP_AWB:
+ __hw_param_copy(&par->isp.awb, &cfg->isp.awb);
+ break;
+
+ case PARAM_ISP_IMAGE_EFFECT:
+ __hw_param_copy(&par->isp.effect, &cfg->isp.effect);
+ break;
+
+ case PARAM_ISP_ISO:
+ __hw_param_copy(&par->isp.iso, &cfg->isp.iso);
+ break;
+
+ case PARAM_ISP_ADJUST:
+ __hw_param_copy(&par->isp.adjust, &cfg->isp.adjust);
+ break;
+
+ case PARAM_ISP_METERING:
+ __hw_param_copy(&par->isp.metering, &cfg->isp.metering);
+ break;
+
+ case PARAM_ISP_AFC:
+ __hw_param_copy(&par->isp.afc, &cfg->isp.afc);
+ break;
+
+ case PARAM_ISP_OTF_OUTPUT:
+ __hw_param_copy(&par->isp.otf_output, &cfg->isp.otf_output);
+ break;
+
+ case PARAM_ISP_DMA1_OUTPUT:
+ __hw_param_copy(&par->isp.dma1_output, &cfg->isp.dma1_output);
+ break;
+
+ case PARAM_ISP_DMA2_OUTPUT:
+ __hw_param_copy(&par->isp.dma2_output, &cfg->isp.dma2_output);
+ break;
+
+ case PARAM_DRC_CONTROL:
+ __hw_param_copy(&par->drc.control, &cfg->drc.control);
+ break;
+
+ case PARAM_DRC_OTF_INPUT:
+ __hw_param_copy(&par->drc.otf_input, &cfg->drc.otf_input);
+ break;
+
+ case PARAM_DRC_DMA_INPUT:
+ __hw_param_copy(&par->drc.dma_input, &cfg->drc.dma_input);
+ break;
+
+ case PARAM_DRC_OTF_OUTPUT:
+ __hw_param_copy(&par->drc.otf_output, &cfg->drc.otf_output);
+ break;
+
+ case PARAM_FD_CONTROL:
+ __hw_param_copy(&par->fd.control, &cfg->fd.control);
+ break;
+
+ case PARAM_FD_OTF_INPUT:
+ __hw_param_copy(&par->fd.otf_input, &cfg->fd.otf_input);
+ break;
+
+ case PARAM_FD_DMA_INPUT:
+ __hw_param_copy(&par->fd.dma_input, &cfg->fd.dma_input);
+ break;
+
+ case PARAM_FD_CONFIG:
+ __hw_param_copy(&par->fd.config, &cfg->fd.config);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+unsigned int __get_pending_param_count(struct fimc_is *is)
+{
+ struct chain_config *config = &is->config[is->config_index];
+ unsigned long flags;
+ unsigned int count;
+
+ spin_lock_irqsave(&is->slock, flags);
+ count = hweight32(config->p_region_index[0]);
+ count += hweight32(config->p_region_index[1]);
+ spin_unlock_irqrestore(&is->slock, flags);
+
+ return count;
+}
+
+int __is_hw_update_params(struct fimc_is *is)
+{
+ unsigned long *p_index;
+ int i, id, ret = 0;
+
+ id = is->config_index;
+ p_index = &is->config[id].p_region_index[0];
+
+ if (test_bit(PARAM_GLOBAL_SHOTMODE, p_index))
+ __fimc_is_hw_update_param_global_shotmode(is);
+
+ if (test_bit(PARAM_SENSOR_FRAME_RATE, p_index))
+ __fimc_is_hw_update_param_sensor_framerate(is);
+
+ for (i = PARAM_ISP_CONTROL; i < PARAM_DRC_CONTROL; i++) {
+ if (test_bit(i, p_index))
+ ret = __fimc_is_hw_update_param(is, i);
+ }
+
+ for (i = PARAM_DRC_CONTROL; i < PARAM_SCALERC_CONTROL; i++) {
+ if (test_bit(i, p_index))
+ ret = __fimc_is_hw_update_param(is, i);
+ }
+
+ for (i = PARAM_FD_CONTROL; i <= PARAM_FD_CONFIG; i++) {
+ if (test_bit(i, p_index))
+ ret = __fimc_is_hw_update_param(is, i);
+ }
+
+ return ret;
+}
+
+void __is_get_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf)
+{
+ struct isp_param *isp;
+
+ isp = &is->config[is->config_index].isp;
+ mf->width = isp->otf_input.width;
+ mf->height = isp->otf_input.height;
+}
+
+void __is_set_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+ struct drc_param *drc;
+ struct fd_param *fd;
+
+ isp = &is->config[index].isp;
+ drc = &is->config[index].drc;
+ fd = &is->config[index].fd;
+
+ /* Update isp size info (OTF only) */
+ isp->otf_input.width = mf->width;
+ isp->otf_input.height = mf->height;
+ isp->otf_output.width = mf->width;
+ isp->otf_output.height = mf->height;
+ /* Update drc size info (OTF only) */
+ drc->otf_input.width = mf->width;
+ drc->otf_input.height = mf->height;
+ drc->otf_output.width = mf->width;
+ drc->otf_output.height = mf->height;
+ /* Update fd size info (OTF only) */
+ fd->otf_input.width = mf->width;
+ fd->otf_input.height = mf->height;
+
+ if (test_bit(PARAM_ISP_OTF_INPUT,
+ &is->config[index].p_region_index[0]))
+ return;
+
+ /* Update field */
+ fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT);
+ fimc_is_set_param_bit(is, PARAM_ISP_OTF_OUTPUT);
+ fimc_is_set_param_bit(is, PARAM_DRC_OTF_INPUT);
+ fimc_is_set_param_bit(is, PARAM_DRC_OTF_OUTPUT);
+ fimc_is_set_param_bit(is, PARAM_FD_OTF_INPUT);
+}
+
+int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *is)
+{
+ switch (is->sensor->drvdata->id) {
+ case FIMC_IS_SENSOR_ID_S5K6A3:
+ return 30;
+ default:
+ return 15;
+ }
+}
+
+void __is_set_sensor(struct fimc_is *is, int fps)
+{
+ unsigned int index = is->config_index;
+ struct sensor_param *sensor;
+ struct isp_param *isp;
+
+ sensor = &is->config[index].sensor;
+ isp = &is->config[index].isp;
+
+ if (fps == 0) {
+ sensor->frame_rate.frame_rate =
+ fimc_is_hw_get_sensor_max_framerate(is);
+ isp->otf_input.frametime_min = 0;
+ isp->otf_input.frametime_max = 66666;
+ } else {
+ sensor->frame_rate.frame_rate = fps;
+ isp->otf_input.frametime_min = 0;
+ isp->otf_input.frametime_max = (u32)1000000 / fps;
+ }
+
+ fimc_is_set_param_bit(is, PARAM_SENSOR_FRAME_RATE);
+ fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT);
+}
+
+static void __maybe_unused __is_set_init_isp_aa(struct fimc_is *is)
+{
+ struct isp_param *isp;
+
+ isp = &is->config[is->config_index].isp;
+
+ isp->aa.cmd = ISP_AA_COMMAND_START;
+ isp->aa.target = ISP_AA_TARGET_AF | ISP_AA_TARGET_AE |
+ ISP_AA_TARGET_AWB;
+ isp->aa.mode = 0;
+ isp->aa.scene = 0;
+ isp->aa.sleep = 0;
+ isp->aa.face = 0;
+ isp->aa.touch_x = 0;
+ isp->aa.touch_y = 0;
+ isp->aa.manual_af_setting = 0;
+ isp->aa.err = ISP_AF_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_AA);
+}
+
+void __is_set_isp_flash(struct fimc_is *is, u32 cmd, u32 redeye)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp = &is->config[index].isp;
+
+ isp->flash.cmd = cmd;
+ isp->flash.redeye = redeye;
+ isp->flash.err = ISP_FLASH_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_FLASH);
+}
+
+void __is_set_isp_awb(struct fimc_is *is, u32 cmd, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+
+ isp = &is->config[index].isp;
+
+ isp->awb.cmd = cmd;
+ isp->awb.illumination = val;
+ isp->awb.err = ISP_AWB_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_AWB);
+}
+
+void __is_set_isp_effect(struct fimc_is *is, u32 cmd)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+
+ isp = &is->config[index].isp;
+
+ isp->effect.cmd = cmd;
+ isp->effect.err = ISP_IMAGE_EFFECT_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_IMAGE_EFFECT);
+}
+
+void __is_set_isp_iso(struct fimc_is *is, u32 cmd, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+
+ isp = &is->config[index].isp;
+
+ isp->iso.cmd = cmd;
+ isp->iso.value = val;
+ isp->iso.err = ISP_ISO_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_ISO);
+}
+
+void __is_set_isp_adjust(struct fimc_is *is, u32 cmd, u32 val)
+{
+ unsigned int index = is->config_index;
+ unsigned long *p_index;
+ struct isp_param *isp;
+
+ p_index = &is->config[index].p_region_index[0];
+ isp = &is->config[index].isp;
+
+ switch (cmd) {
+ case ISP_ADJUST_COMMAND_MANUAL_CONTRAST:
+ isp->adjust.contrast = val;
+ break;
+ case ISP_ADJUST_COMMAND_MANUAL_SATURATION:
+ isp->adjust.saturation = val;
+ break;
+ case ISP_ADJUST_COMMAND_MANUAL_SHARPNESS:
+ isp->adjust.sharpness = val;
+ break;
+ case ISP_ADJUST_COMMAND_MANUAL_EXPOSURE:
+ isp->adjust.exposure = val;
+ break;
+ case ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS:
+ isp->adjust.brightness = val;
+ break;
+ case ISP_ADJUST_COMMAND_MANUAL_HUE:
+ isp->adjust.hue = val;
+ break;
+ case ISP_ADJUST_COMMAND_AUTO:
+ isp->adjust.contrast = 0;
+ isp->adjust.saturation = 0;
+ isp->adjust.sharpness = 0;
+ isp->adjust.exposure = 0;
+ isp->adjust.brightness = 0;
+ isp->adjust.hue = 0;
+ break;
+ }
+
+ if (!test_bit(PARAM_ISP_ADJUST, p_index)) {
+ isp->adjust.cmd = cmd;
+ isp->adjust.err = ISP_ADJUST_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_ADJUST);
+ } else {
+ isp->adjust.cmd |= cmd;
+ }
+}
+
+void __is_set_isp_metering(struct fimc_is *is, u32 id, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[0];
+ isp = &is->config[index].isp;
+
+ switch (id) {
+ case IS_METERING_CONFIG_CMD:
+ isp->metering.cmd = val;
+ break;
+ case IS_METERING_CONFIG_WIN_POS_X:
+ isp->metering.win_pos_x = val;
+ break;
+ case IS_METERING_CONFIG_WIN_POS_Y:
+ isp->metering.win_pos_y = val;
+ break;
+ case IS_METERING_CONFIG_WIN_WIDTH:
+ isp->metering.win_width = val;
+ break;
+ case IS_METERING_CONFIG_WIN_HEIGHT:
+ isp->metering.win_height = val;
+ break;
+ default:
+ return;
+ }
+
+ if (!test_bit(PARAM_ISP_METERING, p_index)) {
+ isp->metering.err = ISP_METERING_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_METERING);
+ }
+}
+
+void __is_set_isp_afc(struct fimc_is *is, u32 cmd, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct isp_param *isp;
+
+ isp = &is->config[index].isp;
+
+ isp->afc.cmd = cmd;
+ isp->afc.manual = val;
+ isp->afc.err = ISP_AFC_ERROR_NONE;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_AFC);
+}
+
+void __is_set_drc_control(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct drc_param *drc;
+
+ drc = &is->config[index].drc;
+
+ drc->control.bypass = val;
+
+ fimc_is_set_param_bit(is, PARAM_DRC_CONTROL);
+}
+
+void __is_set_fd_control(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->control.cmd = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index))
+ fimc_is_set_param_bit(is, PARAM_FD_CONTROL);
+}
+
+void __is_set_fd_config_maxface(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.max_number = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_MAXIMUM_NUMBER;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_MAXIMUM_NUMBER;
+ }
+}
+
+void __is_set_fd_config_rollangle(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.roll_angle = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_ROLL_ANGLE;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_ROLL_ANGLE;
+ }
+}
+
+void __is_set_fd_config_yawangle(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.yaw_angle = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_YAW_ANGLE;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_YAW_ANGLE;
+ }
+}
+
+void __is_set_fd_config_smilemode(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.smile_mode = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_SMILE_MODE;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_SMILE_MODE;
+ }
+}
+
+void __is_set_fd_config_blinkmode(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.blink_mode = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_BLINK_MODE;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_BLINK_MODE;
+ }
+}
+
+void __is_set_fd_config_eyedetect(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.eye_detect = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_EYES_DETECT;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_EYES_DETECT;
+ }
+}
+
+void __is_set_fd_config_mouthdetect(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.mouth_detect = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_MOUTH_DETECT;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_MOUTH_DETECT;
+ }
+}
+
+void __is_set_fd_config_orientation(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.orientation = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_ORIENTATION;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_ORIENTATION;
+ }
+}
+
+void __is_set_fd_config_orientation_val(struct fimc_is *is, u32 val)
+{
+ unsigned int index = is->config_index;
+ struct fd_param *fd;
+ unsigned long *p_index;
+
+ p_index = &is->config[index].p_region_index[1];
+ fd = &is->config[index].fd;
+
+ fd->config.orientation_value = val;
+
+ if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) {
+ fd->config.cmd = FD_CONFIG_COMMAND_ORIENTATION_VALUE;
+ fd->config.err = ERROR_FD_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_CONFIG);
+ } else {
+ fd->config.cmd |= FD_CONFIG_COMMAND_ORIENTATION_VALUE;
+ }
+}
+
+void fimc_is_set_initial_params(struct fimc_is *is)
+{
+ struct global_param *global;
+ struct isp_param *isp;
+ struct drc_param *drc;
+ struct fd_param *fd;
+ unsigned long *p_index;
+ unsigned int index;
+
+ index = is->config_index;
+ global = &is->config[index].global;
+ isp = &is->config[index].isp;
+ drc = &is->config[index].drc;
+ fd = &is->config[index].fd;
+ p_index = &is->config[index].p_region_index[0];
+
+ /* Global */
+ global->shotmode.cmd = 1;
+ fimc_is_set_param_bit(is, PARAM_GLOBAL_SHOTMODE);
+
+ /* ISP */
+ isp->control.cmd = CONTROL_COMMAND_START;
+ isp->control.bypass = CONTROL_BYPASS_DISABLE;
+ isp->control.err = CONTROL_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_CONTROL);
+
+ isp->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE;
+ if (!test_bit(PARAM_ISP_OTF_INPUT, p_index)) {
+ isp->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ isp->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT);
+ }
+ if (is->sensor->test_pattern)
+ isp->otf_input.format = OTF_INPUT_FORMAT_STRGEN_COLORBAR_BAYER;
+ else
+ isp->otf_input.format = OTF_INPUT_FORMAT_BAYER;
+ isp->otf_input.bitwidth = 10;
+ isp->otf_input.order = OTF_INPUT_ORDER_BAYER_GR_BG;
+ isp->otf_input.crop_offset_x = 0;
+ isp->otf_input.crop_offset_y = 0;
+ isp->otf_input.err = OTF_INPUT_ERROR_NONE;
+
+ isp->dma1_input.cmd = DMA_INPUT_COMMAND_DISABLE;
+ isp->dma1_input.width = 0;
+ isp->dma1_input.height = 0;
+ isp->dma1_input.format = 0;
+ isp->dma1_input.bitwidth = 0;
+ isp->dma1_input.plane = 0;
+ isp->dma1_input.order = 0;
+ isp->dma1_input.buffer_number = 0;
+ isp->dma1_input.width = 0;
+ isp->dma1_input.err = DMA_INPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA1_INPUT);
+
+ isp->dma2_input.cmd = DMA_INPUT_COMMAND_DISABLE;
+ isp->dma2_input.width = 0;
+ isp->dma2_input.height = 0;
+ isp->dma2_input.format = 0;
+ isp->dma2_input.bitwidth = 0;
+ isp->dma2_input.plane = 0;
+ isp->dma2_input.order = 0;
+ isp->dma2_input.buffer_number = 0;
+ isp->dma2_input.width = 0;
+ isp->dma2_input.err = DMA_INPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA2_INPUT);
+
+ isp->aa.cmd = ISP_AA_COMMAND_START;
+ isp->aa.target = ISP_AA_TARGET_AE | ISP_AA_TARGET_AWB;
+ fimc_is_set_param_bit(is, PARAM_ISP_AA);
+
+ if (!test_bit(PARAM_ISP_FLASH, p_index))
+ __is_set_isp_flash(is, ISP_FLASH_COMMAND_DISABLE,
+ ISP_FLASH_REDEYE_DISABLE);
+
+ if (!test_bit(PARAM_ISP_AWB, p_index))
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_AUTO, 0);
+
+ if (!test_bit(PARAM_ISP_IMAGE_EFFECT, p_index))
+ __is_set_isp_effect(is, ISP_IMAGE_EFFECT_DISABLE);
+
+ if (!test_bit(PARAM_ISP_ISO, p_index))
+ __is_set_isp_iso(is, ISP_ISO_COMMAND_AUTO, 0);
+
+ if (!test_bit(PARAM_ISP_ADJUST, p_index)) {
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_CONTRAST, 0);
+ __is_set_isp_adjust(is,
+ ISP_ADJUST_COMMAND_MANUAL_SATURATION, 0);
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SHARPNESS, 0);
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_EXPOSURE, 0);
+ __is_set_isp_adjust(is,
+ ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS, 0);
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_HUE, 0);
+ }
+
+ if (!test_bit(PARAM_ISP_METERING, p_index)) {
+ __is_set_isp_metering(is, 0, ISP_METERING_COMMAND_CENTER);
+ __is_set_isp_metering(is, 1, 0);
+ __is_set_isp_metering(is, 2, 0);
+ __is_set_isp_metering(is, 3, 0);
+ __is_set_isp_metering(is, 4, 0);
+ }
+
+ if (!test_bit(PARAM_ISP_AFC, p_index))
+ __is_set_isp_afc(is, ISP_AFC_COMMAND_AUTO, 0);
+
+ isp->otf_output.cmd = OTF_OUTPUT_COMMAND_ENABLE;
+ if (!test_bit(PARAM_ISP_OTF_OUTPUT, p_index)) {
+ isp->otf_output.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ isp->otf_output.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ fimc_is_set_param_bit(is, PARAM_ISP_OTF_OUTPUT);
+ }
+ isp->otf_output.format = OTF_OUTPUT_FORMAT_YUV444;
+ isp->otf_output.bitwidth = 12;
+ isp->otf_output.order = 0;
+ isp->otf_output.err = OTF_OUTPUT_ERROR_NONE;
+
+ if (!test_bit(PARAM_ISP_DMA1_OUTPUT, p_index)) {
+ isp->dma1_output.cmd = DMA_OUTPUT_COMMAND_DISABLE;
+ isp->dma1_output.width = 0;
+ isp->dma1_output.height = 0;
+ isp->dma1_output.format = 0;
+ isp->dma1_output.bitwidth = 0;
+ isp->dma1_output.plane = 0;
+ isp->dma1_output.order = 0;
+ isp->dma1_output.buffer_number = 0;
+ isp->dma1_output.buffer_address = 0;
+ isp->dma1_output.notify_dma_done = 0;
+ isp->dma1_output.dma_out_mask = 0;
+ isp->dma1_output.err = DMA_OUTPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA1_OUTPUT);
+ }
+
+ if (!test_bit(PARAM_ISP_DMA2_OUTPUT, p_index)) {
+ isp->dma2_output.cmd = DMA_OUTPUT_COMMAND_DISABLE;
+ isp->dma2_output.width = 0;
+ isp->dma2_output.height = 0;
+ isp->dma2_output.format = 0;
+ isp->dma2_output.bitwidth = 0;
+ isp->dma2_output.plane = 0;
+ isp->dma2_output.order = 0;
+ isp->dma2_output.buffer_number = 0;
+ isp->dma2_output.buffer_address = 0;
+ isp->dma2_output.notify_dma_done = 0;
+ isp->dma2_output.dma_out_mask = 0;
+ isp->dma2_output.err = DMA_OUTPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT);
+ }
+
+ /* Sensor */
+ if (!test_bit(PARAM_SENSOR_FRAME_RATE, p_index)) {
+ if (is->config_index == 0)
+ __is_set_sensor(is, 0);
+ }
+
+ /* DRC */
+ drc->control.cmd = CONTROL_COMMAND_START;
+ __is_set_drc_control(is, CONTROL_BYPASS_ENABLE);
+
+ drc->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE;
+ if (!test_bit(PARAM_DRC_OTF_INPUT, p_index)) {
+ drc->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ drc->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ fimc_is_set_param_bit(is, PARAM_DRC_OTF_INPUT);
+ }
+ drc->otf_input.format = OTF_INPUT_FORMAT_YUV444;
+ drc->otf_input.bitwidth = 12;
+ drc->otf_input.order = 0;
+ drc->otf_input.err = OTF_INPUT_ERROR_NONE;
+
+ drc->dma_input.cmd = DMA_INPUT_COMMAND_DISABLE;
+ drc->dma_input.width = 0;
+ drc->dma_input.height = 0;
+ drc->dma_input.format = 0;
+ drc->dma_input.bitwidth = 0;
+ drc->dma_input.plane = 0;
+ drc->dma_input.order = 0;
+ drc->dma_input.buffer_number = 0;
+ drc->dma_input.width = 0;
+ drc->dma_input.err = DMA_INPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_DRC_DMA_INPUT);
+
+ drc->otf_output.cmd = OTF_OUTPUT_COMMAND_ENABLE;
+ if (!test_bit(PARAM_DRC_OTF_OUTPUT, p_index)) {
+ drc->otf_output.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ drc->otf_output.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ fimc_is_set_param_bit(is, PARAM_DRC_OTF_OUTPUT);
+ }
+ drc->otf_output.format = OTF_OUTPUT_FORMAT_YUV444;
+ drc->otf_output.bitwidth = 8;
+ drc->otf_output.order = 0;
+ drc->otf_output.err = OTF_OUTPUT_ERROR_NONE;
+
+ /* FD */
+ __is_set_fd_control(is, CONTROL_COMMAND_STOP);
+ fd->control.bypass = CONTROL_BYPASS_DISABLE;
+
+ fd->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE;
+ if (!test_bit(PARAM_FD_OTF_INPUT, p_index)) {
+ fd->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ fd->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ fimc_is_set_param_bit(is, PARAM_FD_OTF_INPUT);
+ }
+
+ fd->otf_input.format = OTF_INPUT_FORMAT_YUV444;
+ fd->otf_input.bitwidth = 8;
+ fd->otf_input.order = 0;
+ fd->otf_input.err = OTF_INPUT_ERROR_NONE;
+
+ fd->dma_input.cmd = DMA_INPUT_COMMAND_DISABLE;
+ fd->dma_input.width = 0;
+ fd->dma_input.height = 0;
+ fd->dma_input.format = 0;
+ fd->dma_input.bitwidth = 0;
+ fd->dma_input.plane = 0;
+ fd->dma_input.order = 0;
+ fd->dma_input.buffer_number = 0;
+ fd->dma_input.width = 0;
+ fd->dma_input.err = DMA_INPUT_ERROR_NONE;
+ fimc_is_set_param_bit(is, PARAM_FD_DMA_INPUT);
+
+ __is_set_fd_config_maxface(is, 5);
+ __is_set_fd_config_rollangle(is, FD_CONFIG_ROLL_ANGLE_FULL);
+ __is_set_fd_config_yawangle(is, FD_CONFIG_YAW_ANGLE_45_90);
+ __is_set_fd_config_smilemode(is, FD_CONFIG_SMILE_MODE_DISABLE);
+ __is_set_fd_config_blinkmode(is, FD_CONFIG_BLINK_MODE_DISABLE);
+ __is_set_fd_config_eyedetect(is, FD_CONFIG_EYES_DETECT_ENABLE);
+ __is_set_fd_config_mouthdetect(is, FD_CONFIG_MOUTH_DETECT_DISABLE);
+ __is_set_fd_config_orientation(is, FD_CONFIG_ORIENTATION_DISABLE);
+ __is_set_fd_config_orientation_val(is, 0);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h
new file mode 100644
index 0000000000..2069046749
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h
@@ -0,0 +1,1022 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#ifndef FIMC_IS_PARAM_H_
+#define FIMC_IS_PARAM_H_
+
+#include <linux/compiler.h>
+
+#define FIMC_IS_CONFIG_TIMEOUT 3000 /* ms */
+#define IS_DEFAULT_WIDTH 1280
+#define IS_DEFAULT_HEIGHT 720
+
+#define DEFAULT_PREVIEW_STILL_WIDTH IS_DEFAULT_WIDTH
+#define DEFAULT_PREVIEW_STILL_HEIGHT IS_DEFAULT_HEIGHT
+#define DEFAULT_CAPTURE_STILL_WIDTH IS_DEFAULT_WIDTH
+#define DEFAULT_CAPTURE_STILL_HEIGHT IS_DEFAULT_HEIGHT
+#define DEFAULT_PREVIEW_VIDEO_WIDTH IS_DEFAULT_WIDTH
+#define DEFAULT_PREVIEW_VIDEO_HEIGHT IS_DEFAULT_HEIGHT
+#define DEFAULT_CAPTURE_VIDEO_WIDTH IS_DEFAULT_WIDTH
+#define DEFAULT_CAPTURE_VIDEO_HEIGHT IS_DEFAULT_HEIGHT
+
+#define DEFAULT_PREVIEW_STILL_FRAMERATE 30
+#define DEFAULT_CAPTURE_STILL_FRAMERATE 15
+#define DEFAULT_PREVIEW_VIDEO_FRAMERATE 30
+#define DEFAULT_CAPTURE_VIDEO_FRAMERATE 30
+
+#define FIMC_IS_REGION_VER 124 /* IS REGION VERSION 1.24 */
+#define FIMC_IS_PARAM_SIZE (FIMC_IS_REGION_SIZE + 1)
+#define FIMC_IS_MAGIC_NUMBER 0x01020304
+#define FIMC_IS_PARAM_MAX_SIZE 64 /* in bytes */
+#define FIMC_IS_PARAM_MAX_ENTRIES (FIMC_IS_PARAM_MAX_SIZE / 4)
+
+/* The parameter bitmask bit definitions. */
+enum is_param_bit {
+ PARAM_GLOBAL_SHOTMODE,
+ PARAM_SENSOR_CONTROL,
+ PARAM_SENSOR_OTF_OUTPUT,
+ PARAM_SENSOR_FRAME_RATE,
+ PARAM_BUFFER_CONTROL,
+ PARAM_BUFFER_OTF_INPUT,
+ PARAM_BUFFER_OTF_OUTPUT,
+ PARAM_ISP_CONTROL,
+ PARAM_ISP_OTF_INPUT,
+ PARAM_ISP_DMA1_INPUT,
+ /* 10 */
+ PARAM_ISP_DMA2_INPUT,
+ PARAM_ISP_AA,
+ PARAM_ISP_FLASH,
+ PARAM_ISP_AWB,
+ PARAM_ISP_IMAGE_EFFECT,
+ PARAM_ISP_ISO,
+ PARAM_ISP_ADJUST,
+ PARAM_ISP_METERING,
+ PARAM_ISP_AFC,
+ PARAM_ISP_OTF_OUTPUT,
+ /* 20 */
+ PARAM_ISP_DMA1_OUTPUT,
+ PARAM_ISP_DMA2_OUTPUT,
+ PARAM_DRC_CONTROL,
+ PARAM_DRC_OTF_INPUT,
+ PARAM_DRC_DMA_INPUT,
+ PARAM_DRC_OTF_OUTPUT,
+ PARAM_SCALERC_CONTROL,
+ PARAM_SCALERC_OTF_INPUT,
+ PARAM_SCALERC_IMAGE_EFFECT,
+ PARAM_SCALERC_INPUT_CROP,
+ /* 30 */
+ PARAM_SCALERC_OUTPUT_CROP,
+ PARAM_SCALERC_OTF_OUTPUT,
+ PARAM_SCALERC_DMA_OUTPUT,
+ PARAM_ODC_CONTROL,
+ PARAM_ODC_OTF_INPUT,
+ PARAM_ODC_OTF_OUTPUT,
+ PARAM_DIS_CONTROL,
+ PARAM_DIS_OTF_INPUT,
+ PARAM_DIS_OTF_OUTPUT,
+ PARAM_TDNR_CONTROL,
+ /* 40 */
+ PARAM_TDNR_OTF_INPUT,
+ PARAM_TDNR_1ST_FRAME,
+ PARAM_TDNR_OTF_OUTPUT,
+ PARAM_TDNR_DMA_OUTPUT,
+ PARAM_SCALERP_CONTROL,
+ PARAM_SCALERP_OTF_INPUT,
+ PARAM_SCALERP_IMAGE_EFFECT,
+ PARAM_SCALERP_INPUT_CROP,
+ PARAM_SCALERP_OUTPUT_CROP,
+ PARAM_SCALERP_ROTATION,
+ /* 50 */
+ PARAM_SCALERP_FLIP,
+ PARAM_SCALERP_OTF_OUTPUT,
+ PARAM_SCALERP_DMA_OUTPUT,
+ PARAM_FD_CONTROL,
+ PARAM_FD_OTF_INPUT,
+ PARAM_FD_DMA_INPUT,
+ PARAM_FD_CONFIG,
+};
+
+/* Interrupt map */
+#define FIMC_IS_INT_GENERAL 0
+#define FIMC_IS_INT_FRAME_DONE_ISP 1
+
+/* Input */
+
+#define CONTROL_COMMAND_STOP 0
+#define CONTROL_COMMAND_START 1
+
+#define CONTROL_BYPASS_DISABLE 0
+#define CONTROL_BYPASS_ENABLE 1
+
+#define CONTROL_ERROR_NONE 0
+
+/* OTF (On-The-Fly) input interface commands */
+#define OTF_INPUT_COMMAND_DISABLE 0
+#define OTF_INPUT_COMMAND_ENABLE 1
+
+/* OTF input interface color formats */
+enum oft_input_fmt {
+ OTF_INPUT_FORMAT_BAYER = 0, /* 1 channel */
+ OTF_INPUT_FORMAT_YUV444 = 1, /* 3 channels */
+ OTF_INPUT_FORMAT_YUV422 = 2, /* 3 channels */
+ OTF_INPUT_FORMAT_YUV420 = 3, /* 3 channels */
+ OTF_INPUT_FORMAT_STRGEN_COLORBAR_BAYER = 10,
+ OTF_INPUT_FORMAT_BAYER_DMA = 11,
+};
+
+#define OTF_INPUT_ORDER_BAYER_GR_BG 0
+
+/* OTF input error codes */
+#define OTF_INPUT_ERROR_NONE 0 /* Input setting is done */
+
+/* DMA input commands */
+#define DMA_INPUT_COMMAND_DISABLE 0
+#define DMA_INPUT_COMMAND_ENABLE 1
+
+/* DMA input color formats */
+enum dma_input_fmt {
+ DMA_INPUT_FORMAT_BAYER = 0,
+ DMA_INPUT_FORMAT_YUV444 = 1,
+ DMA_INPUT_FORMAT_YUV422 = 2,
+ DMA_INPUT_FORMAT_YUV420 = 3,
+};
+
+enum dma_input_order {
+ /* (for DMA_INPUT_PLANE_3) */
+ DMA_INPUT_ORDER_NO = 0,
+ /* (only valid at DMA_INPUT_PLANE_2) */
+ DMA_INPUT_ORDER_CBCR = 1,
+ /* (only valid at DMA_INPUT_PLANE_2) */
+ DMA_INPUT_ORDER_CRCB = 2,
+ /* (only valid at DMA_INPUT_PLANE_1 & DMA_INPUT_FORMAT_YUV444) */
+ DMA_INPUT_ORDER_YCBCR = 3,
+ /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */
+ DMA_INPUT_ORDER_YYCBCR = 4,
+ /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */
+ DMA_INPUT_ORDER_YCBYCR = 5,
+ /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */
+ DMA_INPUT_ORDER_YCRYCB = 6,
+ /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */
+ DMA_INPUT_ORDER_CBYCRY = 7,
+ /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */
+ DMA_INPUT_ORDER_CRYCBY = 8,
+ /* (only valid at DMA_INPUT_FORMAT_BAYER) */
+ DMA_INPUT_ORDER_GR_BG = 9
+};
+
+#define DMA_INPUT_ERROR_NONE 0 /* DMA input setting
+ is done */
+/*
+ * Data output parameter definitions
+ */
+#define OTF_OUTPUT_CROP_DISABLE 0
+#define OTF_OUTPUT_CROP_ENABLE 1
+
+#define OTF_OUTPUT_COMMAND_DISABLE 0
+#define OTF_OUTPUT_COMMAND_ENABLE 1
+
+enum otf_output_fmt {
+ OTF_OUTPUT_FORMAT_YUV444 = 1,
+ OTF_OUTPUT_FORMAT_YUV422 = 2,
+ OTF_OUTPUT_FORMAT_YUV420 = 3,
+ OTF_OUTPUT_FORMAT_RGB = 4,
+};
+
+#define OTF_OUTPUT_ORDER_BAYER_GR_BG 0
+
+#define OTF_OUTPUT_ERROR_NONE 0 /* Output Setting is done */
+
+#define DMA_OUTPUT_COMMAND_DISABLE 0
+#define DMA_OUTPUT_COMMAND_ENABLE 1
+
+enum dma_output_fmt {
+ DMA_OUTPUT_FORMAT_BAYER = 0,
+ DMA_OUTPUT_FORMAT_YUV444 = 1,
+ DMA_OUTPUT_FORMAT_YUV422 = 2,
+ DMA_OUTPUT_FORMAT_YUV420 = 3,
+ DMA_OUTPUT_FORMAT_RGB = 4,
+};
+
+enum dma_output_order {
+ DMA_OUTPUT_ORDER_NO = 0,
+ /* for DMA_OUTPUT_PLANE_3 */
+ DMA_OUTPUT_ORDER_CBCR = 1,
+ /* only valid at DMA_INPUT_PLANE_2) */
+ DMA_OUTPUT_ORDER_CRCB = 2,
+ /* only valid at DMA_OUTPUT_PLANE_2) */
+ DMA_OUTPUT_ORDER_YYCBCR = 3,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_YCBYCR = 4,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_YCRYCB = 5,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CBYCRY = 6,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CRYCBY = 7,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_YCBCR = 8,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CRYCB = 9,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CRCBY = 10,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CBYCR = 11,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_YCRCB = 12,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_CBCRY = 13,
+ /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */
+ DMA_OUTPUT_ORDER_BGR = 14,
+ /* only valid at DMA_OUTPUT_FORMAT_RGB */
+ DMA_OUTPUT_ORDER_GB_BG = 15
+ /* only valid at DMA_OUTPUT_FORMAT_BAYER */
+};
+
+/* enum dma_output_notify_dma_done */
+#define DMA_OUTPUT_NOTIFY_DMA_DONE_DISABLE 0
+#define DMA_OUTPUT_NOTIFY_DMA_DONE_ENABLE 1
+
+/* DMA output error codes */
+#define DMA_OUTPUT_ERROR_NONE 0 /* DMA output setting
+ is done */
+
+/* ---------------------- Global ----------------------------------- */
+#define GLOBAL_SHOTMODE_ERROR_NONE 0 /* shot-mode setting
+ is done */
+/* 3A lock commands */
+#define ISP_AA_COMMAND_START 0
+#define ISP_AA_COMMAND_STOP 1
+
+/* 3A lock target */
+#define ISP_AA_TARGET_AF 1
+#define ISP_AA_TARGET_AE 2
+#define ISP_AA_TARGET_AWB 4
+
+enum isp_af_mode {
+ ISP_AF_MODE_MANUAL = 0,
+ ISP_AF_MODE_SINGLE = 1,
+ ISP_AF_MODE_CONTINUOUS = 2,
+ ISP_AF_MODE_TOUCH = 3,
+ ISP_AF_MODE_SLEEP = 4,
+ ISP_AF_MODE_INIT = 5,
+ ISP_AF_MODE_SET_CENTER_WINDOW = 6,
+ ISP_AF_MODE_SET_TOUCH_WINDOW = 7
+};
+
+/* Face AF commands */
+#define ISP_AF_FACE_DISABLE 0
+#define ISP_AF_FACE_ENABLE 1
+
+/* AF range */
+#define ISP_AF_RANGE_NORMAL 0
+#define ISP_AF_RANGE_MACRO 1
+
+/* AF sleep */
+#define ISP_AF_SLEEP_OFF 0
+#define ISP_AF_SLEEP_ON 1
+
+/* Continuous AF commands */
+#define ISP_AF_CONTINUOUS_DISABLE 0
+#define ISP_AF_CONTINUOUS_ENABLE 1
+
+/* ISP AF error codes */
+#define ISP_AF_ERROR_NONE 0 /* AF mode change is done */
+#define ISP_AF_ERROR_NONE_LOCK_DONE 1 /* AF lock is done */
+
+/* Flash commands */
+#define ISP_FLASH_COMMAND_DISABLE 0
+#define ISP_FLASH_COMMAND_MANUAL_ON 1 /* (forced flash) */
+#define ISP_FLASH_COMMAND_AUTO 2
+#define ISP_FLASH_COMMAND_TORCH 3 /* 3 sec */
+
+/* Flash red-eye commands */
+#define ISP_FLASH_REDEYE_DISABLE 0
+#define ISP_FLASH_REDEYE_ENABLE 1
+
+/* Flash error codes */
+#define ISP_FLASH_ERROR_NONE 0 /* Flash setting is done */
+
+/* -------------------------- AWB ------------------------------------ */
+enum isp_awb_command {
+ ISP_AWB_COMMAND_AUTO = 0,
+ ISP_AWB_COMMAND_ILLUMINATION = 1,
+ ISP_AWB_COMMAND_MANUAL = 2
+};
+
+enum isp_awb_illumination {
+ ISP_AWB_ILLUMINATION_DAYLIGHT = 0,
+ ISP_AWB_ILLUMINATION_CLOUDY = 1,
+ ISP_AWB_ILLUMINATION_TUNGSTEN = 2,
+ ISP_AWB_ILLUMINATION_FLUORESCENT = 3
+};
+
+/* ISP AWN error codes */
+#define ISP_AWB_ERROR_NONE 0 /* AWB setting is done */
+
+/* -------------------------- Effect ----------------------------------- */
+enum isp_imageeffect_command {
+ ISP_IMAGE_EFFECT_DISABLE = 0,
+ ISP_IMAGE_EFFECT_MONOCHROME = 1,
+ ISP_IMAGE_EFFECT_NEGATIVE_MONO = 2,
+ ISP_IMAGE_EFFECT_NEGATIVE_COLOR = 3,
+ ISP_IMAGE_EFFECT_SEPIA = 4
+};
+
+/* Image effect error codes */
+#define ISP_IMAGE_EFFECT_ERROR_NONE 0 /* Image effect setting
+ is done */
+/* ISO commands */
+#define ISP_ISO_COMMAND_AUTO 0
+#define ISP_ISO_COMMAND_MANUAL 1
+
+/* ISO error codes */
+#define ISP_ISO_ERROR_NONE 0 /* ISO setting is done */
+
+/* ISP adjust commands */
+#define ISP_ADJUST_COMMAND_AUTO (0 << 0)
+#define ISP_ADJUST_COMMAND_MANUAL_CONTRAST (1 << 0)
+#define ISP_ADJUST_COMMAND_MANUAL_SATURATION (1 << 1)
+#define ISP_ADJUST_COMMAND_MANUAL_SHARPNESS (1 << 2)
+#define ISP_ADJUST_COMMAND_MANUAL_EXPOSURE (1 << 3)
+#define ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS (1 << 4)
+#define ISP_ADJUST_COMMAND_MANUAL_HUE (1 << 5)
+#define ISP_ADJUST_COMMAND_MANUAL_ALL 0x7f
+
+/* ISP adjustment error codes */
+#define ISP_ADJUST_ERROR_NONE 0 /* Adjust setting is done */
+
+/*
+ * Exposure metering
+ */
+enum isp_metering_command {
+ ISP_METERING_COMMAND_AVERAGE = 0,
+ ISP_METERING_COMMAND_SPOT = 1,
+ ISP_METERING_COMMAND_MATRIX = 2,
+ ISP_METERING_COMMAND_CENTER = 3
+};
+
+/* ISP metering error codes */
+#define ISP_METERING_ERROR_NONE 0 /* Metering setting is done */
+
+/*
+ * AFC
+ */
+enum isp_afc_command {
+ ISP_AFC_COMMAND_DISABLE = 0,
+ ISP_AFC_COMMAND_AUTO = 1,
+ ISP_AFC_COMMAND_MANUAL = 2,
+};
+
+#define ISP_AFC_MANUAL_50HZ 50
+#define ISP_AFC_MANUAL_60HZ 60
+
+/* ------------------------ SCENE MODE--------------------------------- */
+enum isp_scene_mode {
+ ISP_SCENE_NONE = 0,
+ ISP_SCENE_PORTRAIT = 1,
+ ISP_SCENE_LANDSCAPE = 2,
+ ISP_SCENE_SPORTS = 3,
+ ISP_SCENE_PARTYINDOOR = 4,
+ ISP_SCENE_BEACHSNOW = 5,
+ ISP_SCENE_SUNSET = 6,
+ ISP_SCENE_DAWN = 7,
+ ISP_SCENE_FALL = 8,
+ ISP_SCENE_NIGHT = 9,
+ ISP_SCENE_AGAINSTLIGHTWLIGHT = 10,
+ ISP_SCENE_AGAINSTLIGHTWOLIGHT = 11,
+ ISP_SCENE_FIRE = 12,
+ ISP_SCENE_TEXT = 13,
+ ISP_SCENE_CANDLE = 14
+};
+
+/* AFC error codes */
+#define ISP_AFC_ERROR_NONE 0 /* AFC setting is done */
+
+/* ---------------------------- FD ------------------------------------- */
+enum fd_config_command {
+ FD_CONFIG_COMMAND_MAXIMUM_NUMBER = 0x1,
+ FD_CONFIG_COMMAND_ROLL_ANGLE = 0x2,
+ FD_CONFIG_COMMAND_YAW_ANGLE = 0x4,
+ FD_CONFIG_COMMAND_SMILE_MODE = 0x8,
+ FD_CONFIG_COMMAND_BLINK_MODE = 0x10,
+ FD_CONFIG_COMMAND_EYES_DETECT = 0x20,
+ FD_CONFIG_COMMAND_MOUTH_DETECT = 0x40,
+ FD_CONFIG_COMMAND_ORIENTATION = 0x80,
+ FD_CONFIG_COMMAND_ORIENTATION_VALUE = 0x100
+};
+
+enum fd_config_roll_angle {
+ FD_CONFIG_ROLL_ANGLE_BASIC = 0,
+ FD_CONFIG_ROLL_ANGLE_PRECISE_BASIC = 1,
+ FD_CONFIG_ROLL_ANGLE_SIDES = 2,
+ FD_CONFIG_ROLL_ANGLE_PRECISE_SIDES = 3,
+ FD_CONFIG_ROLL_ANGLE_FULL = 4,
+ FD_CONFIG_ROLL_ANGLE_PRECISE_FULL = 5,
+};
+
+enum fd_config_yaw_angle {
+ FD_CONFIG_YAW_ANGLE_0 = 0,
+ FD_CONFIG_YAW_ANGLE_45 = 1,
+ FD_CONFIG_YAW_ANGLE_90 = 2,
+ FD_CONFIG_YAW_ANGLE_45_90 = 3,
+};
+
+/* Smile mode configuration */
+#define FD_CONFIG_SMILE_MODE_DISABLE 0
+#define FD_CONFIG_SMILE_MODE_ENABLE 1
+
+/* Blink mode configuration */
+#define FD_CONFIG_BLINK_MODE_DISABLE 0
+#define FD_CONFIG_BLINK_MODE_ENABLE 1
+
+/* Eyes detection configuration */
+#define FD_CONFIG_EYES_DETECT_DISABLE 0
+#define FD_CONFIG_EYES_DETECT_ENABLE 1
+
+/* Mouth detection configuration */
+#define FD_CONFIG_MOUTH_DETECT_DISABLE 0
+#define FD_CONFIG_MOUTH_DETECT_ENABLE 1
+
+#define FD_CONFIG_ORIENTATION_DISABLE 0
+#define FD_CONFIG_ORIENTATION_ENABLE 1
+
+struct param_control {
+ u32 cmd;
+ u32 bypass;
+ u32 buffer_address;
+ u32 buffer_size;
+ u32 skip_frames; /* only valid at ISP */
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 6];
+ u32 err;
+};
+
+struct param_otf_input {
+ u32 cmd;
+ u32 width;
+ u32 height;
+ u32 format;
+ u32 bitwidth;
+ u32 order;
+ u32 crop_offset_x;
+ u32 crop_offset_y;
+ u32 crop_width;
+ u32 crop_height;
+ u32 frametime_min;
+ u32 frametime_max;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 13];
+ u32 err;
+};
+
+struct param_dma_input {
+ u32 cmd;
+ u32 width;
+ u32 height;
+ u32 format;
+ u32 bitwidth;
+ u32 plane;
+ u32 order;
+ u32 buffer_number;
+ u32 buffer_address;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10];
+ u32 err;
+};
+
+struct param_otf_output {
+ u32 cmd;
+ u32 width;
+ u32 height;
+ u32 format;
+ u32 bitwidth;
+ u32 order;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 7];
+ u32 err;
+};
+
+struct param_dma_output {
+ u32 cmd;
+ u32 width;
+ u32 height;
+ u32 format;
+ u32 bitwidth;
+ u32 plane;
+ u32 order;
+ u32 buffer_number;
+ u32 buffer_address;
+ u32 notify_dma_done;
+ u32 dma_out_mask;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 12];
+ u32 err;
+};
+
+struct param_global_shotmode {
+ u32 cmd;
+ u32 skip_frames;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3];
+ u32 err;
+};
+
+struct param_sensor_framerate {
+ u32 frame_rate;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2];
+ u32 err;
+};
+
+struct param_isp_aa {
+ u32 cmd;
+ u32 target;
+ u32 mode;
+ u32 scene;
+ u32 sleep;
+ u32 face;
+ u32 touch_x;
+ u32 touch_y;
+ u32 manual_af_setting;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10];
+ u32 err;
+};
+
+struct param_isp_flash {
+ u32 cmd;
+ u32 redeye;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3];
+ u32 err;
+};
+
+struct param_isp_awb {
+ u32 cmd;
+ u32 illumination;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3];
+ u32 err;
+};
+
+struct param_isp_imageeffect {
+ u32 cmd;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2];
+ u32 err;
+};
+
+struct param_isp_iso {
+ u32 cmd;
+ u32 value;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3];
+ u32 err;
+};
+
+struct param_isp_adjust {
+ u32 cmd;
+ s32 contrast;
+ s32 saturation;
+ s32 sharpness;
+ s32 exposure;
+ s32 brightness;
+ s32 hue;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 8];
+ u32 err;
+};
+
+struct param_isp_metering {
+ u32 cmd;
+ u32 win_pos_x;
+ u32 win_pos_y;
+ u32 win_width;
+ u32 win_height;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 6];
+ u32 err;
+};
+
+struct param_isp_afc {
+ u32 cmd;
+ u32 manual;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3];
+ u32 err;
+};
+
+struct param_scaler_imageeffect {
+ u32 cmd;
+ u32 arbitrary_cb;
+ u32 arbitrary_cr;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 4];
+ u32 err;
+};
+
+struct param_scaler_input_crop {
+ u32 cmd;
+ u32 crop_offset_x;
+ u32 crop_offset_y;
+ u32 crop_width;
+ u32 crop_height;
+ u32 in_width;
+ u32 in_height;
+ u32 out_width;
+ u32 out_height;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10];
+ u32 err;
+};
+
+struct param_scaler_output_crop {
+ u32 cmd;
+ u32 crop_offset_x;
+ u32 crop_offset_y;
+ u32 crop_width;
+ u32 crop_height;
+ u32 out_format;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 7];
+ u32 err;
+};
+
+struct param_scaler_rotation {
+ u32 cmd;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2];
+ u32 err;
+};
+
+struct param_scaler_flip {
+ u32 cmd;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2];
+ u32 err;
+};
+
+struct param_3dnr_1stframe {
+ u32 cmd;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2];
+ u32 err;
+};
+
+struct param_fd_config {
+ u32 cmd;
+ u32 max_number;
+ u32 roll_angle;
+ u32 yaw_angle;
+ u32 smile_mode;
+ u32 blink_mode;
+ u32 eye_detect;
+ u32 mouth_detect;
+ u32 orientation;
+ u32 orientation_value;
+ u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 11];
+ u32 err;
+};
+
+struct global_param {
+ struct param_global_shotmode shotmode;
+};
+
+struct sensor_param {
+ struct param_control control;
+ struct param_otf_output otf_output;
+ struct param_sensor_framerate frame_rate;
+} __packed;
+
+struct buffer_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_otf_output otf_output;
+} __packed;
+
+struct isp_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_dma_input dma1_input;
+ struct param_dma_input dma2_input;
+ struct param_isp_aa aa;
+ struct param_isp_flash flash;
+ struct param_isp_awb awb;
+ struct param_isp_imageeffect effect;
+ struct param_isp_iso iso;
+ struct param_isp_adjust adjust;
+ struct param_isp_metering metering;
+ struct param_isp_afc afc;
+ struct param_otf_output otf_output;
+ struct param_dma_output dma1_output;
+ struct param_dma_output dma2_output;
+} __packed;
+
+struct drc_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_dma_input dma_input;
+ struct param_otf_output otf_output;
+} __packed;
+
+struct scalerc_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_scaler_imageeffect effect;
+ struct param_scaler_input_crop input_crop;
+ struct param_scaler_output_crop output_crop;
+ struct param_otf_output otf_output;
+ struct param_dma_output dma_output;
+} __packed;
+
+struct odc_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_otf_output otf_output;
+} __packed;
+
+struct dis_param {
+ struct param_control control;
+ struct param_otf_output otf_input;
+ struct param_otf_output otf_output;
+} __packed;
+
+struct tdnr_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_3dnr_1stframe frame;
+ struct param_otf_output otf_output;
+ struct param_dma_output dma_output;
+} __packed;
+
+struct scalerp_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_scaler_imageeffect effect;
+ struct param_scaler_input_crop input_crop;
+ struct param_scaler_output_crop output_crop;
+ struct param_scaler_rotation rotation;
+ struct param_scaler_flip flip;
+ struct param_otf_output otf_output;
+ struct param_dma_output dma_output;
+} __packed;
+
+struct fd_param {
+ struct param_control control;
+ struct param_otf_input otf_input;
+ struct param_dma_input dma_input;
+ struct param_fd_config config;
+} __packed;
+
+struct is_param_region {
+ struct global_param global;
+ struct sensor_param sensor;
+ struct buffer_param buf;
+ struct isp_param isp;
+ struct drc_param drc;
+ struct scalerc_param scalerc;
+ struct odc_param odc;
+ struct dis_param dis;
+ struct tdnr_param tdnr;
+ struct scalerp_param scalerp;
+ struct fd_param fd;
+} __packed;
+
+#define NUMBER_OF_GAMMA_CURVE_POINTS 32
+
+struct is_tune_sensor {
+ u32 exposure;
+ u32 analog_gain;
+ u32 frame_rate;
+ u32 actuator_position;
+};
+
+struct is_tune_gammacurve {
+ u32 num_pts_x[NUMBER_OF_GAMMA_CURVE_POINTS];
+ u32 num_pts_y_r[NUMBER_OF_GAMMA_CURVE_POINTS];
+ u32 num_pts_y_g[NUMBER_OF_GAMMA_CURVE_POINTS];
+ u32 num_pts_y_b[NUMBER_OF_GAMMA_CURVE_POINTS];
+};
+
+struct is_tune_isp {
+ /* Brightness level: range 0...100, default 7. */
+ u32 brightness_level;
+ /* Contrast level: range -127...127, default 0. */
+ s32 contrast_level;
+ /* Saturation level: range -127...127, default 0. */
+ s32 saturation_level;
+ s32 gamma_level;
+ struct is_tune_gammacurve gamma_curve[4];
+ /* Hue: range -127...127, default 0. */
+ s32 hue;
+ /* Sharpness blur: range -127...127, default 0. */
+ s32 sharpness_blur;
+ /* Despeckle : range -127~127, default : 0 */
+ s32 despeckle;
+ /* Edge color supression: range -127...127, default 0. */
+ s32 edge_color_supression;
+ /* Noise reduction: range -127...127, default 0. */
+ s32 noise_reduction;
+ /* (32 * 4 + 9) * 4 = 548 bytes */
+} __packed;
+
+struct is_tune_region {
+ struct is_tune_sensor sensor;
+ struct is_tune_isp isp;
+} __packed;
+
+struct rational {
+ u32 num;
+ u32 den;
+};
+
+struct srational {
+ s32 num;
+ s32 den;
+};
+
+#define FLASH_FIRED_SHIFT 0
+#define FLASH_NOT_FIRED 0
+#define FLASH_FIRED 1
+
+#define FLASH_STROBE_SHIFT 1
+#define FLASH_STROBE_NO_DETECTION 0
+#define FLASH_STROBE_RESERVED 1
+#define FLASH_STROBE_RETURN_LIGHT_NOT_DETECTED 2
+#define FLASH_STROBE_RETURN_LIGHT_DETECTED 3
+
+#define FLASH_MODE_SHIFT 3
+#define FLASH_MODE_UNKNOWN 0
+#define FLASH_MODE_COMPULSORY_FLASH_FIRING 1
+#define FLASH_MODE_COMPULSORY_FLASH_SUPPRESSION 2
+#define FLASH_MODE_AUTO_MODE 3
+
+#define FLASH_FUNCTION_SHIFT 5
+#define FLASH_FUNCTION_PRESENT 0
+#define FLASH_FUNCTION_NONE 1
+
+#define FLASH_RED_EYE_SHIFT 6
+#define FLASH_RED_EYE_DISABLED 0
+#define FLASH_RED_EYE_SUPPORTED 1
+
+enum apex_aperture_value {
+ F1_0 = 0,
+ F1_4 = 1,
+ F2_0 = 2,
+ F2_8 = 3,
+ F4_0 = 4,
+ F5_6 = 5,
+ F8_9 = 6,
+ F11_0 = 7,
+ F16_0 = 8,
+ F22_0 = 9,
+ F32_0 = 10,
+};
+
+struct exif_attribute {
+ struct rational exposure_time;
+ struct srational shutter_speed;
+ u32 iso_speed_rating;
+ u32 flash;
+ struct srational brightness;
+} __packed;
+
+struct is_frame_header {
+ u32 valid;
+ u32 bad_mark;
+ u32 captured;
+ u32 frame_number;
+ struct exif_attribute exif;
+} __packed;
+
+struct is_fd_rect {
+ u32 offset_x;
+ u32 offset_y;
+ u32 width;
+ u32 height;
+};
+
+struct is_face_marker {
+ u32 frame_number;
+ struct is_fd_rect face;
+ struct is_fd_rect left_eye;
+ struct is_fd_rect right_eye;
+ struct is_fd_rect mouth;
+ u32 roll_angle;
+ u32 yaw_angle;
+ u32 confidence;
+ s32 smile_level;
+ s32 blink_level;
+} __packed;
+
+#define MAX_FRAME_COUNT 8
+#define MAX_FRAME_COUNT_PREVIEW 4
+#define MAX_FRAME_COUNT_CAPTURE 1
+#define MAX_FACE_COUNT 16
+#define MAX_SHARED_COUNT 500
+
+struct is_region {
+ struct is_param_region parameter;
+ struct is_tune_region tune;
+ struct is_frame_header header[MAX_FRAME_COUNT];
+ struct is_face_marker face[MAX_FACE_COUNT];
+ u32 shared[MAX_SHARED_COUNT];
+} __packed;
+
+/* Offset to the ISP DMA2 output buffer address array. */
+#define DMA2_OUTPUT_ADDR_ARRAY_OFFS \
+ (offsetof(struct is_region, shared) + 32 * sizeof(u32))
+
+struct is_debug_frame_descriptor {
+ u32 sensor_frame_time;
+ u32 sensor_exposure_time;
+ s32 sensor_analog_gain;
+ /* monitor for AA */
+ u32 req_lei;
+
+ u32 next_next_lei_exp;
+ u32 next_next_lei_a_gain;
+ u32 next_next_lei_d_gain;
+ u32 next_next_lei_statlei;
+ u32 next_next_lei_lei;
+
+ u32 dummy0;
+};
+
+#define MAX_FRAMEDESCRIPTOR_CONTEXT_NUM (30*20) /* 600 frames */
+#define MAX_VERSION_DISPLAY_BUF 32
+
+struct is_share_region {
+ u32 frame_time;
+ u32 exposure_time;
+ s32 analog_gain;
+
+ u32 r_gain;
+ u32 g_gain;
+ u32 b_gain;
+
+ u32 af_position;
+ u32 af_status;
+ /* 0 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_NOMESSAGE */
+ /* 1 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_REACHED */
+ /* 2 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_UNABLETOREACH */
+ /* 3 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_LOST */
+ /* default : unknown */
+ u32 af_scene_type;
+
+ u32 frame_descp_onoff_control;
+ u32 frame_descp_update_done;
+ u32 frame_descp_idx;
+ u32 frame_descp_max_idx;
+ struct is_debug_frame_descriptor
+ dbg_frame_descp_ctx[MAX_FRAMEDESCRIPTOR_CONTEXT_NUM];
+
+ u32 chip_id;
+ u32 chip_rev_no;
+ u8 isp_fw_ver_no[MAX_VERSION_DISPLAY_BUF];
+ u8 isp_fw_ver_date[MAX_VERSION_DISPLAY_BUF];
+ u8 sirc_sdk_ver_no[MAX_VERSION_DISPLAY_BUF];
+ u8 sirc_sdk_rev_no[MAX_VERSION_DISPLAY_BUF];
+ u8 sirc_sdk_rev_date[MAX_VERSION_DISPLAY_BUF];
+} __packed;
+
+struct is_debug_control {
+ u32 write_point; /* 0~ 500KB boundary */
+ u32 assert_flag; /* 0: Not invoked, 1: Invoked */
+ u32 pabort_flag; /* 0: Not invoked, 1: Invoked */
+ u32 dabort_flag; /* 0: Not invoked, 1: Invoked */
+};
+
+struct sensor_open_extended {
+ u32 actuator_type;
+ u32 mclk;
+ u32 mipi_lane_num;
+ u32 mipi_speed;
+ /* Skip setfile loading when fast_open_sensor is not 0 */
+ u32 fast_open_sensor;
+ /* Activating sensor self calibration mode (6A3) */
+ u32 self_calibration_mode;
+ /* This field is to adjust I2c clock based on ACLK200 */
+ /* This value is varied in case of rev 0.2 */
+ u32 i2c_sclk;
+};
+
+struct fimc_is;
+
+int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *is);
+int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset);
+void fimc_is_set_initial_params(struct fimc_is *is);
+unsigned int __get_pending_param_count(struct fimc_is *is);
+
+int __is_hw_update_params(struct fimc_is *is);
+void __is_get_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf);
+void __is_set_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf);
+void __is_set_sensor(struct fimc_is *is, int fps);
+void __is_set_isp_aa_ae(struct fimc_is *is);
+void __is_set_isp_flash(struct fimc_is *is, u32 cmd, u32 redeye);
+void __is_set_isp_awb(struct fimc_is *is, u32 cmd, u32 val);
+void __is_set_isp_effect(struct fimc_is *is, u32 cmd);
+void __is_set_isp_iso(struct fimc_is *is, u32 cmd, u32 val);
+void __is_set_isp_adjust(struct fimc_is *is, u32 cmd, u32 val);
+void __is_set_isp_metering(struct fimc_is *is, u32 id, u32 val);
+void __is_set_isp_afc(struct fimc_is *is, u32 cmd, u32 val);
+void __is_set_drc_control(struct fimc_is *is, u32 val);
+void __is_set_fd_control(struct fimc_is *is, u32 val);
+void __is_set_fd_config_maxface(struct fimc_is *is, u32 val);
+void __is_set_fd_config_rollangle(struct fimc_is *is, u32 val);
+void __is_set_fd_config_yawangle(struct fimc_is *is, u32 val);
+void __is_set_fd_config_smilemode(struct fimc_is *is, u32 val);
+void __is_set_fd_config_blinkmode(struct fimc_is *is, u32 val);
+void __is_set_fd_config_eyedetect(struct fimc_is *is, u32 val);
+void __is_set_fd_config_mouthdetect(struct fimc_is *is, u32 val);
+void __is_set_fd_config_orientation(struct fimc_is *is, u32 val);
+void __is_set_fd_config_orientation_val(struct fimc_is *is, u32 val);
+void __is_set_isp_aa_af_mode(struct fimc_is *is, int cmd);
+void __is_set_isp_aa_af_start_stop(struct fimc_is *is, int cmd);
+
+#endif
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c
new file mode 100644
index 0000000000..366e639381
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#include <linux/delay.h>
+
+#include "fimc-is.h"
+#include "fimc-is-command.h"
+#include "fimc-is-regs.h"
+#include "fimc-is-sensor.h"
+
+void fimc_is_fw_clear_irq1(struct fimc_is *is, unsigned int nr)
+{
+ mcuctl_write(1UL << nr, is, MCUCTL_REG_INTCR1);
+}
+
+void fimc_is_fw_clear_irq2(struct fimc_is *is)
+{
+ u32 cfg = mcuctl_read(is, MCUCTL_REG_INTSR2);
+ mcuctl_write(cfg, is, MCUCTL_REG_INTCR2);
+}
+
+void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is)
+{
+ mcuctl_write(INTGR0_INTGD(0), is, MCUCTL_REG_INTGR0);
+}
+
+int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is)
+{
+ unsigned int timeout = 2000;
+ u32 cfg, status;
+
+ do {
+ cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0);
+ status = INTMSR0_GET_INTMSD(0, cfg);
+
+ if (--timeout == 0) {
+ dev_warn(&is->pdev->dev, "%s timeout\n",
+ __func__);
+ return -ETIMEDOUT;
+ }
+ udelay(1);
+ } while (status != 0);
+
+ return 0;
+}
+
+int fimc_is_hw_set_param(struct fimc_is *is)
+{
+ struct chain_config *config = &is->config[is->config_index];
+ unsigned int param_count = __get_pending_param_count(is);
+
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+
+ mcuctl_write(HIC_SET_PARAMETER, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(is->config_index, is, MCUCTL_REG_ISSR(2));
+
+ mcuctl_write(param_count, is, MCUCTL_REG_ISSR(3));
+ mcuctl_write(config->p_region_index[0], is, MCUCTL_REG_ISSR(4));
+ mcuctl_write(config->p_region_index[1], is, MCUCTL_REG_ISSR(5));
+
+ fimc_is_hw_set_intgr0_gd0(is);
+ return 0;
+}
+
+static int __maybe_unused fimc_is_hw_set_tune(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+
+ mcuctl_write(HIC_SET_TUNE, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(is->h2i_cmd.entry_id, is, MCUCTL_REG_ISSR(2));
+
+ fimc_is_hw_set_intgr0_gd0(is);
+ return 0;
+}
+
+#define FIMC_IS_MAX_PARAMS 4
+
+int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num_args)
+{
+ int i;
+
+ if (num_args > FIMC_IS_MAX_PARAMS)
+ return -EINVAL;
+
+ is->i2h_cmd.num_args = num_args;
+
+ for (i = 0; i < FIMC_IS_MAX_PARAMS; i++) {
+ if (i < num_args)
+ is->i2h_cmd.args[i] = mcuctl_read(is,
+ MCUCTL_REG_ISSR(12 + i));
+ else
+ is->i2h_cmd.args[i] = 0;
+ }
+ return 0;
+}
+
+void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask)
+{
+ if (hweight32(mask) == 1) {
+ dev_err(&is->pdev->dev, "%s(): not enough buffers (mask %#x)\n",
+ __func__, mask);
+ return;
+ }
+
+ if (mcuctl_read(is, MCUCTL_REG_ISSR(23)) != 0)
+ dev_dbg(&is->pdev->dev, "non-zero DMA buffer mask\n");
+
+ mcuctl_write(mask, is, MCUCTL_REG_ISSR(23));
+}
+
+void fimc_is_hw_set_sensor_num(struct fimc_is *is)
+{
+ pr_debug("setting sensor index to: %d\n", is->sensor_index);
+
+ mcuctl_write(IH_REPLY_DONE, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(IHC_GET_SENSOR_NUM, is, MCUCTL_REG_ISSR(2));
+ mcuctl_write(FIMC_IS_SENSORS_NUM, is, MCUCTL_REG_ISSR(3));
+}
+
+void fimc_is_hw_close_sensor(struct fimc_is *is, unsigned int index)
+{
+ if (is->sensor_index != index)
+ return;
+
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_CLOSE_SENSOR, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(2));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+void fimc_is_hw_get_setfile_addr(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_GET_SET_FILE_ADDR, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+void fimc_is_hw_load_setfile(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_LOAD_SET_FILE, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+int fimc_is_hw_change_mode(struct fimc_is *is)
+{
+ static const u8 cmd[] = {
+ HIC_PREVIEW_STILL, HIC_PREVIEW_VIDEO,
+ HIC_CAPTURE_STILL, HIC_CAPTURE_VIDEO,
+ };
+
+ if (WARN_ON(is->config_index >= ARRAY_SIZE(cmd)))
+ return -EINVAL;
+
+ mcuctl_write(cmd[is->config_index], is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(is->setfile.sub_index, is, MCUCTL_REG_ISSR(2));
+ fimc_is_hw_set_intgr0_gd0(is);
+ return 0;
+}
+
+void fimc_is_hw_stream_on(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_STREAM_ON, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(0, is, MCUCTL_REG_ISSR(2));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+void fimc_is_hw_stream_off(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_STREAM_OFF, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+void fimc_is_hw_subip_power_off(struct fimc_is *is)
+{
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ mcuctl_write(HIC_POWER_DOWN, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ fimc_is_hw_set_intgr0_gd0(is);
+}
+
+int fimc_is_itf_s_param(struct fimc_is *is, bool update)
+{
+ int ret;
+
+ if (update)
+ __is_hw_update_params(is);
+
+ fimc_is_mem_barrier();
+
+ clear_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state);
+ fimc_is_hw_set_param(is);
+ ret = fimc_is_wait_event(is, IS_ST_BLOCK_CMD_CLEARED, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0)
+ dev_err(&is->pdev->dev, "%s() timeout\n", __func__);
+
+ return ret;
+}
+
+int fimc_is_itf_mode_change(struct fimc_is *is)
+{
+ int ret;
+
+ clear_bit(IS_ST_CHANGE_MODE, &is->state);
+ fimc_is_hw_change_mode(is);
+ ret = fimc_is_wait_event(is, IS_ST_CHANGE_MODE, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0)
+ dev_err(&is->pdev->dev, "%s(): mode change (%d) timeout\n",
+ __func__, is->config_index);
+ return ret;
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h
new file mode 100644
index 0000000000..5d8b01bc84
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ * Younghwan Joo <yhwan.joo@samsung.com>
+ */
+#ifndef FIMC_IS_REG_H_
+#define FIMC_IS_REG_H_
+
+/* WDT_ISP register */
+#define REG_WDT_ISP 0x00170000
+
+/* MCUCTL registers base offset */
+#define MCUCTL_BASE 0x00180000
+
+/* MCU Controller Register */
+#define MCUCTL_REG_MCUCTRL (MCUCTL_BASE + 0x00)
+#define MCUCTRL_MSWRST (1 << 0)
+
+/* Boot Base Offset Address Register */
+#define MCUCTL_REG_BBOAR (MCUCTL_BASE + 0x04)
+
+/* Interrupt Generation Register 0 from Host CPU to VIC */
+#define MCUCTL_REG_INTGR0 (MCUCTL_BASE + 0x08)
+/* __n = 0...9 */
+#define INTGR0_INTGC(__n) (1 << ((__n) + 16))
+/* __n = 0...5 */
+#define INTGR0_INTGD(__n) (1 << (__n))
+
+/* Interrupt Clear Register 0 from Host CPU to VIC */
+#define MCUCTL_REG_INTCR0 (MCUCTL_BASE + 0x0c)
+/* __n = 0...9 */
+#define INTCR0_INTGC(__n) (1 << ((__n) + 16))
+/* __n = 0...5 */
+#define INTCR0_INTCD(__n) (1 << ((__n) + 16))
+
+/* Interrupt Mask Register 0 from Host CPU to VIC */
+#define MCUCTL_REG_INTMR0 (MCUCTL_BASE + 0x10)
+/* __n = 0...9 */
+#define INTMR0_INTMC(__n) (1 << ((__n) + 16))
+/* __n = 0...5 */
+#define INTMR0_INTMD(__n) (1 << (__n))
+
+/* Interrupt Status Register 0 from Host CPU to VIC */
+#define MCUCTL_REG_INTSR0 (MCUCTL_BASE + 0x14)
+/* __n (bit number) = 0...4 */
+#define INTSR0_GET_INTSD(x, __n) (((x) >> (__n)) & 0x1)
+/* __n (bit number) = 0...9 */
+#define INTSR0_GET_INTSC(x, __n) (((x) >> ((__n) + 16)) & 0x1)
+
+/* Interrupt Mask Status Register 0 from Host CPU to VIC */
+#define MCUCTL_REG_INTMSR0 (MCUCTL_BASE + 0x18)
+/* __n (bit number) = 0...4 */
+#define INTMSR0_GET_INTMSD(x, __n) (((x) >> (__n)) & 0x1)
+/* __n (bit number) = 0...9 */
+#define INTMSR0_GET_INTMSC(x, __n) (((x) >> ((__n) + 16)) & 0x1)
+
+/* Interrupt Generation Register 1 from ISP CPU to Host IC */
+#define MCUCTL_REG_INTGR1 (MCUCTL_BASE + 0x1c)
+/* __n = 0...9 */
+#define INTGR1_INTGC(__n) (1 << (__n))
+
+/* Interrupt Clear Register 1 from ISP CPU to Host IC */
+#define MCUCTL_REG_INTCR1 (MCUCTL_BASE + 0x20)
+/* __n = 0...9 */
+#define INTCR1_INTCC(__n) (1 << (__n))
+
+/* Interrupt Mask Register 1 from ISP CPU to Host IC */
+#define MCUCTL_REG_INTMR1 (MCUCTL_BASE + 0x24)
+/* __n = 0...9 */
+#define INTMR1_INTMC(__n) (1 << (__n))
+
+/* Interrupt Status Register 1 from ISP CPU to Host IC */
+#define MCUCTL_REG_INTSR1 (MCUCTL_BASE + 0x28)
+/* Interrupt Mask Status Register 1 from ISP CPU to Host IC */
+#define MCUCTL_REG_INTMSR1 (MCUCTL_BASE + 0x2c)
+
+/* Interrupt Clear Register 2 from ISP BLK's interrupts to Host IC */
+#define MCUCTL_REG_INTCR2 (MCUCTL_BASE + 0x30)
+/* __n = 0...5 */
+#define INTCR2_INTCC(__n) (1 << ((__n) + 16))
+
+/* Interrupt Mask Register 2 from ISP BLK's interrupts to Host IC */
+#define MCUCTL_REG_INTMR2 (MCUCTL_BASE + 0x34)
+/* __n = 0...25 */
+#define INTMR2_INTMCIS(__n) (1 << (__n))
+
+/* Interrupt Status Register 2 from ISP BLK's interrupts to Host IC */
+#define MCUCTL_REG_INTSR2 (MCUCTL_BASE + 0x38)
+/* Interrupt Mask Status Register 2 from ISP BLK's interrupts to Host IC */
+#define MCUCTL_REG_INTMSR2 (MCUCTL_BASE + 0x3c)
+
+/* General Purpose Output Control Register (0~17) */
+#define MCUCTL_REG_GPOCTLR (MCUCTL_BASE + 0x40)
+/* __n = 0...17 */
+#define GPOCTLR_GPOG(__n) (1 << (__n))
+
+/* General Purpose Pad Output Enable Register (0~17) */
+#define MCUCTL_REG_GPOENCTLR (MCUCTL_BASE + 0x44)
+/* __n = 0...17 */
+#define GPOENCTLR_GPOEN(__n) (1 << (__n))
+
+/* General Purpose Input Control Register (0~17) */
+#define MCUCTL_REG_GPICTLR (MCUCTL_BASE + 0x48)
+
+/* Shared registers between ISP CPU and the host CPU - ISSRxx */
+
+/* ISSR(1): Command Host -> IS */
+/* ISSR(1): Sensor ID for Command, ISSR2...5 = Parameter 1...4 */
+
+/* ISSR(10): Reply IS -> Host */
+/* ISSR(11): Sensor ID for Reply, ISSR12...15 = Parameter 1...4 */
+
+/* ISSR(20): ISP_FRAME_DONE : SENSOR ID */
+/* ISSR(21): ISP_FRAME_DONE : PARAMETER 1 */
+
+/* ISSR(24): SCALERC_FRAME_DONE : SENSOR ID */
+/* ISSR(25): SCALERC_FRAME_DONE : PARAMETER 1 */
+
+/* ISSR(28): 3DNR_FRAME_DONE : SENSOR ID */
+/* ISSR(29): 3DNR_FRAME_DONE : PARAMETER 1 */
+
+/* ISSR(32): SCALERP_FRAME_DONE : SENSOR ID */
+/* ISSR(33): SCALERP_FRAME_DONE : PARAMETER 1 */
+
+/* __n = 0...63 */
+#define MCUCTL_REG_ISSR(__n) (MCUCTL_BASE + 0x80 + ((__n) * 4))
+
+/* PMU ISP register offsets */
+#define REG_CMU_RESET_ISP_SYS_PWR_REG 0x1174
+#define REG_CMU_SYSCLK_ISP_SYS_PWR_REG 0x13b8
+#define REG_PMU_ISP_ARM_SYS 0x1050
+#define REG_PMU_ISP_ARM_CONFIGURATION 0x2280
+#define REG_PMU_ISP_ARM_STATUS 0x2284
+#define REG_PMU_ISP_ARM_OPTION 0x2288
+
+void fimc_is_fw_clear_irq1(struct fimc_is *is, unsigned int bit);
+void fimc_is_fw_clear_irq2(struct fimc_is *is);
+int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num);
+
+void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is);
+int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is);
+void fimc_is_hw_set_sensor_num(struct fimc_is *is);
+void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask);
+void fimc_is_hw_stream_on(struct fimc_is *is);
+void fimc_is_hw_stream_off(struct fimc_is *is);
+int fimc_is_hw_set_param(struct fimc_is *is);
+int fimc_is_hw_change_mode(struct fimc_is *is);
+
+void fimc_is_hw_close_sensor(struct fimc_is *is, unsigned int index);
+void fimc_is_hw_get_setfile_addr(struct fimc_is *is);
+void fimc_is_hw_load_setfile(struct fimc_is *is);
+void fimc_is_hw_subip_power_off(struct fimc_is *is);
+
+int fimc_is_itf_s_param(struct fimc_is *is, bool update);
+int fimc_is_itf_mode_change(struct fimc_is *is);
+
+#endif /* FIMC_IS_REG_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c
new file mode 100644
index 0000000000..0e5b9fede4
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include "fimc-is-sensor.h"
+
+static const struct sensor_drv_data s5k6a3_drvdata = {
+ .id = FIMC_IS_SENSOR_ID_S5K6A3,
+ .open_timeout = S5K6A3_OPEN_TIMEOUT,
+};
+
+static const struct of_device_id fimc_is_sensor_of_ids[] = {
+ {
+ .compatible = "samsung,s5k6a3",
+ .data = &s5k6a3_drvdata,
+ },
+ { }
+};
+
+const struct sensor_drv_data *fimc_is_sensor_get_drvdata(
+ struct device_node *node)
+{
+ const struct of_device_id *of_id;
+
+ of_id = of_match_node(fimc_is_sensor_of_ids, node);
+ return of_id ? of_id->data : NULL;
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h
new file mode 100644
index 0000000000..9aefc63889
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ * Younghwan Joo <yhwan.joo@samsung.com>
+ */
+#ifndef FIMC_IS_SENSOR_H_
+#define FIMC_IS_SENSOR_H_
+
+#include <linux/of.h>
+#include <linux/types.h>
+
+#define S5K6A3_OPEN_TIMEOUT 2000 /* ms */
+#define S5K6A3_SENSOR_WIDTH 1392
+#define S5K6A3_SENSOR_HEIGHT 1392
+
+enum fimc_is_sensor_id {
+ FIMC_IS_SENSOR_ID_S5K3H2 = 1,
+ FIMC_IS_SENSOR_ID_S5K6A3,
+ FIMC_IS_SENSOR_ID_S5K4E5,
+ FIMC_IS_SENSOR_ID_S5K3H7,
+ FIMC_IS_SENSOR_ID_CUSTOM,
+ FIMC_IS_SENSOR_ID_END
+};
+
+#define IS_SENSOR_CTRL_BUS_I2C0 0
+#define IS_SENSOR_CTRL_BUS_I2C1 1
+
+struct sensor_drv_data {
+ enum fimc_is_sensor_id id;
+ /* sensor open timeout in ms */
+ unsigned short open_timeout;
+};
+
+/**
+ * struct fimc_is_sensor - fimc-is sensor data structure
+ * @drvdata: a pointer to the sensor's parameters data structure
+ * @i2c_bus: ISP I2C bus index (0...1)
+ * @test_pattern: true to enable video test pattern
+ */
+struct fimc_is_sensor {
+ const struct sensor_drv_data *drvdata;
+ unsigned int i2c_bus;
+ u8 test_pattern;
+};
+
+const struct sensor_drv_data *fimc_is_sensor_get_drvdata(
+ struct device_node *node);
+
+#endif /* FIMC_IS_SENSOR_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.c b/drivers/media/platform/samsung/exynos4-is/fimc-is.c
new file mode 100644
index 0000000000..530a148fe4
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.c
@@ -0,0 +1,987 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ * Younghwan Joo <yhwan.joo@samsung.com>
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "media-dev.h"
+#include "fimc-is.h"
+#include "fimc-is-command.h"
+#include "fimc-is-errno.h"
+#include "fimc-is-i2c.h"
+#include "fimc-is-param.h"
+#include "fimc-is-regs.h"
+
+
+static char *fimc_is_clocks[ISS_CLKS_MAX] = {
+ [ISS_CLK_PPMUISPX] = "ppmuispx",
+ [ISS_CLK_PPMUISPMX] = "ppmuispmx",
+ [ISS_CLK_LITE0] = "lite0",
+ [ISS_CLK_LITE1] = "lite1",
+ [ISS_CLK_MPLL] = "mpll",
+ [ISS_CLK_ISP] = "isp",
+ [ISS_CLK_DRC] = "drc",
+ [ISS_CLK_FD] = "fd",
+ [ISS_CLK_MCUISP] = "mcuisp",
+ [ISS_CLK_GICISP] = "gicisp",
+ [ISS_CLK_PWM_ISP] = "pwm_isp",
+ [ISS_CLK_MCUCTL_ISP] = "mcuctl_isp",
+ [ISS_CLK_UART] = "uart",
+ [ISS_CLK_ISP_DIV0] = "ispdiv0",
+ [ISS_CLK_ISP_DIV1] = "ispdiv1",
+ [ISS_CLK_MCUISP_DIV0] = "mcuispdiv0",
+ [ISS_CLK_MCUISP_DIV1] = "mcuispdiv1",
+ [ISS_CLK_ACLK200] = "aclk200",
+ [ISS_CLK_ACLK200_DIV] = "div_aclk200",
+ [ISS_CLK_ACLK400MCUISP] = "aclk400mcuisp",
+ [ISS_CLK_ACLK400MCUISP_DIV] = "div_aclk400mcuisp",
+};
+
+static void fimc_is_put_clocks(struct fimc_is *is)
+{
+ int i;
+
+ for (i = 0; i < ISS_CLKS_MAX; i++) {
+ if (IS_ERR(is->clocks[i]))
+ continue;
+ clk_put(is->clocks[i]);
+ is->clocks[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int fimc_is_get_clocks(struct fimc_is *is)
+{
+ int i, ret;
+
+ for (i = 0; i < ISS_CLKS_MAX; i++)
+ is->clocks[i] = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < ISS_CLKS_MAX; i++) {
+ is->clocks[i] = clk_get(&is->pdev->dev, fimc_is_clocks[i]);
+ if (IS_ERR(is->clocks[i])) {
+ ret = PTR_ERR(is->clocks[i]);
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ fimc_is_put_clocks(is);
+ dev_err(&is->pdev->dev, "failed to get clock: %s\n",
+ fimc_is_clocks[i]);
+ return ret;
+}
+
+static int fimc_is_setup_clocks(struct fimc_is *is)
+{
+ int ret;
+
+ ret = clk_set_parent(is->clocks[ISS_CLK_ACLK200],
+ is->clocks[ISS_CLK_ACLK200_DIV]);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_set_parent(is->clocks[ISS_CLK_ACLK400MCUISP],
+ is->clocks[ISS_CLK_ACLK400MCUISP_DIV]);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV0], ACLK_AXI_FREQUENCY);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV1], ACLK_AXI_FREQUENCY);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV0],
+ ATCLK_MCUISP_FREQUENCY);
+ if (ret < 0)
+ return ret;
+
+ return clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV1],
+ ATCLK_MCUISP_FREQUENCY);
+}
+
+static int fimc_is_enable_clocks(struct fimc_is *is)
+{
+ int i, ret;
+
+ for (i = 0; i < ISS_GATE_CLKS_MAX; i++) {
+ if (IS_ERR(is->clocks[i]))
+ continue;
+ ret = clk_prepare_enable(is->clocks[i]);
+ if (ret < 0) {
+ dev_err(&is->pdev->dev, "clock %s enable failed\n",
+ fimc_is_clocks[i]);
+ for (--i; i >= 0; i--)
+ clk_disable_unprepare(is->clocks[i]);
+ return ret;
+ }
+ pr_debug("enabled clock: %s\n", fimc_is_clocks[i]);
+ }
+ return 0;
+}
+
+static void fimc_is_disable_clocks(struct fimc_is *is)
+{
+ int i;
+
+ for (i = 0; i < ISS_GATE_CLKS_MAX; i++) {
+ if (!IS_ERR(is->clocks[i])) {
+ clk_disable_unprepare(is->clocks[i]);
+ pr_debug("disabled clock: %s\n", fimc_is_clocks[i]);
+ }
+ }
+}
+
+static int fimc_is_parse_sensor_config(struct fimc_is *is, unsigned int index,
+ struct device_node *node)
+{
+ struct fimc_is_sensor *sensor = &is->sensor[index];
+ struct device_node *ep, *port;
+ u32 tmp = 0;
+ int ret;
+
+ sensor->drvdata = fimc_is_sensor_get_drvdata(node);
+ if (!sensor->drvdata) {
+ dev_err(&is->pdev->dev, "no driver data found for: %pOF\n",
+ node);
+ return -EINVAL;
+ }
+
+ ep = of_graph_get_next_endpoint(node, NULL);
+ if (!ep)
+ return -ENXIO;
+
+ port = of_graph_get_remote_port(ep);
+ of_node_put(ep);
+ if (!port)
+ return -ENXIO;
+
+ /* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */
+ ret = of_property_read_u32(port, "reg", &tmp);
+ if (ret < 0) {
+ dev_err(&is->pdev->dev, "reg property not found at: %pOF\n",
+ port);
+ of_node_put(port);
+ return ret;
+ }
+
+ of_node_put(port);
+ sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0;
+ return 0;
+}
+
+static int fimc_is_register_subdevs(struct fimc_is *is)
+{
+ struct device_node *i2c_bus, *child;
+ int ret, index = 0;
+
+ ret = fimc_isp_subdev_create(&is->isp);
+ if (ret < 0)
+ return ret;
+
+ for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) {
+ for_each_available_child_of_node(i2c_bus, child) {
+ ret = fimc_is_parse_sensor_config(is, index, child);
+
+ if (ret < 0 || index >= FIMC_IS_SENSORS_NUM) {
+ of_node_put(child);
+ of_node_put(i2c_bus);
+ return ret;
+ }
+ index++;
+ }
+ }
+ return 0;
+}
+
+static int fimc_is_unregister_subdevs(struct fimc_is *is)
+{
+ fimc_isp_subdev_destroy(&is->isp);
+ return 0;
+}
+
+static int fimc_is_load_setfile(struct fimc_is *is, char *file_name)
+{
+ const struct firmware *fw;
+ void *buf;
+ int ret;
+
+ ret = request_firmware(&fw, file_name, &is->pdev->dev);
+ if (ret < 0) {
+ dev_err(&is->pdev->dev, "firmware request failed (%d)\n", ret);
+ return ret;
+ }
+ buf = is->memory.vaddr + is->setfile.base;
+ memcpy(buf, fw->data, fw->size);
+ fimc_is_mem_barrier();
+ is->setfile.size = fw->size;
+
+ pr_debug("mem vaddr: %p, setfile buf: %p\n", is->memory.vaddr, buf);
+
+ memcpy(is->fw.setfile_info,
+ fw->data + fw->size - FIMC_IS_SETFILE_INFO_LEN,
+ FIMC_IS_SETFILE_INFO_LEN - 1);
+
+ is->fw.setfile_info[FIMC_IS_SETFILE_INFO_LEN - 1] = '\0';
+ is->setfile.state = 1;
+
+ pr_debug("FIMC-IS setfile loaded: base: %#x, size: %zu B\n",
+ is->setfile.base, fw->size);
+
+ release_firmware(fw);
+ return ret;
+}
+
+int fimc_is_cpu_set_power(struct fimc_is *is, int on)
+{
+ unsigned int timeout = FIMC_IS_POWER_ON_TIMEOUT;
+
+ if (on) {
+ /* Disable watchdog */
+ mcuctl_write(0, is, REG_WDT_ISP);
+
+ /* Cortex-A5 start address setting */
+ mcuctl_write(is->memory.addr, is, MCUCTL_REG_BBOAR);
+
+ /* Enable and start Cortex-A5 */
+ pmuisp_write(0x18000, is, REG_PMU_ISP_ARM_OPTION);
+ pmuisp_write(0x1, is, REG_PMU_ISP_ARM_CONFIGURATION);
+ } else {
+ /* A5 power off */
+ pmuisp_write(0x10000, is, REG_PMU_ISP_ARM_OPTION);
+ pmuisp_write(0x0, is, REG_PMU_ISP_ARM_CONFIGURATION);
+
+ while (pmuisp_read(is, REG_PMU_ISP_ARM_STATUS) & 1) {
+ if (timeout == 0)
+ return -ETIME;
+ timeout--;
+ udelay(1);
+ }
+ }
+
+ return 0;
+}
+
+/* Wait until @bit of @is->state is set to @state in the interrupt handler. */
+int fimc_is_wait_event(struct fimc_is *is, unsigned long bit,
+ unsigned int state, unsigned int timeout)
+{
+
+ int ret = wait_event_timeout(is->irq_queue,
+ !state ^ test_bit(bit, &is->state),
+ timeout);
+ if (ret == 0) {
+ dev_WARN(&is->pdev->dev, "%s() timed out\n", __func__);
+ return -ETIME;
+ }
+ return 0;
+}
+
+int fimc_is_start_firmware(struct fimc_is *is)
+{
+ struct device *dev = &is->pdev->dev;
+ int ret;
+
+ if (is->fw.f_w == NULL) {
+ dev_err(dev, "firmware is not loaded\n");
+ return -EINVAL;
+ }
+
+ memcpy(is->memory.vaddr, is->fw.f_w->data, is->fw.f_w->size);
+ wmb();
+
+ ret = fimc_is_cpu_set_power(is, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = fimc_is_wait_event(is, IS_ST_A5_PWR_ON, 1,
+ msecs_to_jiffies(FIMC_IS_FW_LOAD_TIMEOUT));
+ if (ret < 0)
+ dev_err(dev, "FIMC-IS CPU power on failed\n");
+
+ return ret;
+}
+
+/* Allocate working memory for the FIMC-IS CPU. */
+static int fimc_is_alloc_cpu_memory(struct fimc_is *is)
+{
+ struct device *dev = &is->pdev->dev;
+
+ is->memory.vaddr = dma_alloc_coherent(dev, FIMC_IS_CPU_MEM_SIZE,
+ &is->memory.addr, GFP_KERNEL);
+ if (is->memory.vaddr == NULL)
+ return -ENOMEM;
+
+ is->memory.size = FIMC_IS_CPU_MEM_SIZE;
+
+ dev_info(dev, "FIMC-IS CPU memory base: %pad\n", &is->memory.addr);
+
+ if (((u32)is->memory.addr) & FIMC_IS_FW_ADDR_MASK) {
+ dev_err(dev, "invalid firmware memory alignment: %#x\n",
+ (u32)is->memory.addr);
+ dma_free_coherent(dev, is->memory.size, is->memory.vaddr,
+ is->memory.addr);
+ return -EIO;
+ }
+
+ is->is_p_region = (struct is_region *)(is->memory.vaddr +
+ FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE);
+
+ is->is_dma_p_region = is->memory.addr +
+ FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE;
+
+ is->is_shared_region = (struct is_share_region *)(is->memory.vaddr +
+ FIMC_IS_SHARED_REGION_OFFSET);
+ return 0;
+}
+
+static void fimc_is_free_cpu_memory(struct fimc_is *is)
+{
+ struct device *dev = &is->pdev->dev;
+
+ if (is->memory.vaddr == NULL)
+ return;
+
+ dma_free_coherent(dev, is->memory.size, is->memory.vaddr,
+ is->memory.addr);
+}
+
+static void fimc_is_load_firmware(const struct firmware *fw, void *context)
+{
+ struct fimc_is *is = context;
+ struct device *dev = &is->pdev->dev;
+ void *buf;
+ int ret;
+
+ if (fw == NULL) {
+ dev_err(dev, "firmware request failed\n");
+ return;
+ }
+ mutex_lock(&is->lock);
+
+ if (fw->size < FIMC_IS_FW_SIZE_MIN || fw->size > FIMC_IS_FW_SIZE_MAX) {
+ dev_err(dev, "wrong firmware size: %zu\n", fw->size);
+ goto done;
+ }
+
+ is->fw.size = fw->size;
+
+ ret = fimc_is_alloc_cpu_memory(is);
+ if (ret < 0) {
+ dev_err(dev, "failed to allocate FIMC-IS CPU memory\n");
+ goto done;
+ }
+
+ memcpy(is->memory.vaddr, fw->data, fw->size);
+ wmb();
+
+ /* Read firmware description. */
+ buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_DESC_LEN);
+ memcpy(&is->fw.info, buf, FIMC_IS_FW_INFO_LEN);
+ is->fw.info[FIMC_IS_FW_INFO_LEN] = 0;
+
+ buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_VER_LEN);
+ memcpy(&is->fw.version, buf, FIMC_IS_FW_VER_LEN);
+ is->fw.version[FIMC_IS_FW_VER_LEN - 1] = 0;
+
+ is->fw.state = 1;
+
+ dev_info(dev, "loaded firmware: %s, rev. %s\n",
+ is->fw.info, is->fw.version);
+ dev_dbg(dev, "FW size: %zu, DMA addr: %pad\n", fw->size, &is->memory.addr);
+
+ is->is_shared_region->chip_id = 0xe4412;
+ is->is_shared_region->chip_rev_no = 1;
+
+ fimc_is_mem_barrier();
+
+ /*
+ * FIXME: The firmware is not being released for now, as it is
+ * needed around for copying to the IS working memory every
+ * time before the Cortex-A5 is restarted.
+ */
+ release_firmware(is->fw.f_w);
+ is->fw.f_w = fw;
+done:
+ mutex_unlock(&is->lock);
+}
+
+static int fimc_is_request_firmware(struct fimc_is *is, const char *fw_name)
+{
+ return request_firmware_nowait(THIS_MODULE,
+ FW_ACTION_UEVENT, fw_name, &is->pdev->dev,
+ GFP_KERNEL, is, fimc_is_load_firmware);
+}
+
+/* General IS interrupt handler */
+static void fimc_is_general_irq_handler(struct fimc_is *is)
+{
+ is->i2h_cmd.cmd = mcuctl_read(is, MCUCTL_REG_ISSR(10));
+
+ switch (is->i2h_cmd.cmd) {
+ case IHC_GET_SENSOR_NUM:
+ fimc_is_hw_get_params(is, 1);
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+ fimc_is_hw_set_sensor_num(is);
+ pr_debug("ISP FW version: %#x\n", is->i2h_cmd.args[0]);
+ break;
+ case IHC_SET_FACE_MARK:
+ case IHC_FRAME_DONE:
+ fimc_is_hw_get_params(is, 2);
+ break;
+ case IHC_SET_SHOT_MARK:
+ case IHC_AA_DONE:
+ case IH_REPLY_DONE:
+ fimc_is_hw_get_params(is, 3);
+ break;
+ case IH_REPLY_NOT_DONE:
+ fimc_is_hw_get_params(is, 4);
+ break;
+ case IHC_NOT_READY:
+ break;
+ default:
+ pr_info("unknown command: %#x\n", is->i2h_cmd.cmd);
+ }
+
+ fimc_is_fw_clear_irq1(is, FIMC_IS_INT_GENERAL);
+
+ switch (is->i2h_cmd.cmd) {
+ case IHC_GET_SENSOR_NUM:
+ fimc_is_hw_set_intgr0_gd0(is);
+ set_bit(IS_ST_A5_PWR_ON, &is->state);
+ break;
+
+ case IHC_SET_SHOT_MARK:
+ break;
+
+ case IHC_SET_FACE_MARK:
+ is->fd_header.count = is->i2h_cmd.args[0];
+ is->fd_header.index = is->i2h_cmd.args[1];
+ is->fd_header.offset = 0;
+ break;
+
+ case IHC_FRAME_DONE:
+ break;
+
+ case IHC_AA_DONE:
+ pr_debug("AA_DONE - %d, %d, %d\n", is->i2h_cmd.args[0],
+ is->i2h_cmd.args[1], is->i2h_cmd.args[2]);
+ break;
+
+ case IH_REPLY_DONE:
+ pr_debug("ISR_DONE: args[0]: %#x\n", is->i2h_cmd.args[0]);
+
+ switch (is->i2h_cmd.args[0]) {
+ case HIC_PREVIEW_STILL...HIC_CAPTURE_VIDEO:
+ /* Get CAC margin */
+ set_bit(IS_ST_CHANGE_MODE, &is->state);
+ is->isp.cac_margin_x = is->i2h_cmd.args[1];
+ is->isp.cac_margin_y = is->i2h_cmd.args[2];
+ pr_debug("CAC margin (x,y): (%d,%d)\n",
+ is->isp.cac_margin_x, is->isp.cac_margin_y);
+ break;
+
+ case HIC_STREAM_ON:
+ clear_bit(IS_ST_STREAM_OFF, &is->state);
+ set_bit(IS_ST_STREAM_ON, &is->state);
+ break;
+
+ case HIC_STREAM_OFF:
+ clear_bit(IS_ST_STREAM_ON, &is->state);
+ set_bit(IS_ST_STREAM_OFF, &is->state);
+ break;
+
+ case HIC_SET_PARAMETER:
+ is->config[is->config_index].p_region_index[0] = 0;
+ is->config[is->config_index].p_region_index[1] = 0;
+ set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state);
+ pr_debug("HIC_SET_PARAMETER\n");
+ break;
+
+ case HIC_GET_PARAMETER:
+ break;
+
+ case HIC_SET_TUNE:
+ break;
+
+ case HIC_GET_STATUS:
+ break;
+
+ case HIC_OPEN_SENSOR:
+ set_bit(IS_ST_OPEN_SENSOR, &is->state);
+ pr_debug("data lanes: %d, settle line: %d\n",
+ is->i2h_cmd.args[2], is->i2h_cmd.args[1]);
+ break;
+
+ case HIC_CLOSE_SENSOR:
+ clear_bit(IS_ST_OPEN_SENSOR, &is->state);
+ is->sensor_index = 0;
+ break;
+
+ case HIC_MSG_TEST:
+ pr_debug("config MSG level completed\n");
+ break;
+
+ case HIC_POWER_DOWN:
+ clear_bit(IS_ST_PWR_SUBIP_ON, &is->state);
+ break;
+
+ case HIC_GET_SET_FILE_ADDR:
+ is->setfile.base = is->i2h_cmd.args[1];
+ set_bit(IS_ST_SETFILE_LOADED, &is->state);
+ break;
+
+ case HIC_LOAD_SET_FILE:
+ set_bit(IS_ST_SETFILE_LOADED, &is->state);
+ break;
+ }
+ break;
+
+ case IH_REPLY_NOT_DONE:
+ pr_err("ISR_NDONE: %d: %#x, %s\n", is->i2h_cmd.args[0],
+ is->i2h_cmd.args[1],
+ fimc_is_strerr(is->i2h_cmd.args[1]));
+
+ if (is->i2h_cmd.args[1] & IS_ERROR_TIME_OUT_FLAG)
+ pr_err("IS_ERROR_TIME_OUT\n");
+
+ switch (is->i2h_cmd.args[1]) {
+ case IS_ERROR_SET_PARAMETER:
+ fimc_is_mem_barrier();
+ }
+
+ switch (is->i2h_cmd.args[0]) {
+ case HIC_SET_PARAMETER:
+ is->config[is->config_index].p_region_index[0] = 0;
+ is->config[is->config_index].p_region_index[1] = 0;
+ set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state);
+ break;
+ }
+ break;
+
+ case IHC_NOT_READY:
+ pr_err("IS control sequence error: Not Ready\n");
+ break;
+ }
+
+ wake_up(&is->irq_queue);
+}
+
+static irqreturn_t fimc_is_irq_handler(int irq, void *priv)
+{
+ struct fimc_is *is = priv;
+ unsigned long flags;
+ u32 status;
+
+ spin_lock_irqsave(&is->slock, flags);
+ status = mcuctl_read(is, MCUCTL_REG_INTSR1);
+
+ if (status & (1UL << FIMC_IS_INT_GENERAL))
+ fimc_is_general_irq_handler(is);
+
+ if (status & (1UL << FIMC_IS_INT_FRAME_DONE_ISP))
+ fimc_isp_irq_handler(is);
+
+ spin_unlock_irqrestore(&is->slock, flags);
+ return IRQ_HANDLED;
+}
+
+static int fimc_is_hw_open_sensor(struct fimc_is *is,
+ struct fimc_is_sensor *sensor)
+{
+ struct sensor_open_extended *soe = (void *)&is->is_p_region->shared;
+
+ fimc_is_hw_wait_intmsr0_intmsd0(is);
+
+ soe->self_calibration_mode = 1;
+ soe->actuator_type = 0;
+ soe->mipi_lane_num = 0;
+ soe->mclk = 0;
+ soe->mipi_speed = 0;
+ soe->fast_open_sensor = 0;
+ soe->i2c_sclk = 88000000;
+
+ fimc_is_mem_barrier();
+
+ /*
+ * Some user space use cases hang up here without this
+ * empirically chosen delay.
+ */
+ udelay(100);
+
+ mcuctl_write(HIC_OPEN_SENSOR, is, MCUCTL_REG_ISSR(0));
+ mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1));
+ mcuctl_write(sensor->drvdata->id, is, MCUCTL_REG_ISSR(2));
+ mcuctl_write(sensor->i2c_bus, is, MCUCTL_REG_ISSR(3));
+ mcuctl_write(is->is_dma_p_region, is, MCUCTL_REG_ISSR(4));
+
+ fimc_is_hw_set_intgr0_gd0(is);
+
+ return fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 1,
+ sensor->drvdata->open_timeout);
+}
+
+
+int fimc_is_hw_initialize(struct fimc_is *is)
+{
+ static const int config_ids[] = {
+ IS_SC_PREVIEW_STILL, IS_SC_PREVIEW_VIDEO,
+ IS_SC_CAPTURE_STILL, IS_SC_CAPTURE_VIDEO
+ };
+ struct device *dev = &is->pdev->dev;
+ u32 prev_id;
+ int i, ret;
+
+ /* Sensor initialization. Only one sensor is currently supported. */
+ ret = fimc_is_hw_open_sensor(is, &is->sensor[0]);
+ if (ret < 0)
+ return ret;
+
+ /* Get the setfile address. */
+ fimc_is_hw_get_setfile_addr(is);
+
+ ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev, "get setfile address timed out\n");
+ return ret;
+ }
+ pr_debug("setfile.base: %#x\n", is->setfile.base);
+
+ /* Load the setfile. */
+ fimc_is_load_setfile(is, FIMC_IS_SETFILE_6A3);
+ clear_bit(IS_ST_SETFILE_LOADED, &is->state);
+ fimc_is_hw_load_setfile(is);
+ ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev, "loading setfile timed out\n");
+ return ret;
+ }
+
+ pr_debug("setfile: base: %#x, size: %d\n",
+ is->setfile.base, is->setfile.size);
+ pr_info("FIMC-IS Setfile info: %s\n", is->fw.setfile_info);
+
+ /* Check magic number. */
+ if (is->is_p_region->shared[MAX_SHARED_COUNT - 1] !=
+ FIMC_IS_MAGIC_NUMBER) {
+ dev_err(dev, "magic number error!\n");
+ return -EIO;
+ }
+
+ pr_debug("shared region: %pad, parameter region: %pad\n",
+ &is->memory.addr + FIMC_IS_SHARED_REGION_OFFSET,
+ &is->is_dma_p_region);
+
+ is->setfile.sub_index = 0;
+
+ /* Stream off. */
+ fimc_is_hw_stream_off(is);
+ ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev, "stream off timeout\n");
+ return ret;
+ }
+
+ /* Preserve previous mode. */
+ prev_id = is->config_index;
+
+ /* Set initial parameter values. */
+ for (i = 0; i < ARRAY_SIZE(config_ids); i++) {
+ is->config_index = config_ids[i];
+ fimc_is_set_initial_params(is);
+ ret = fimc_is_itf_s_param(is, true);
+ if (ret < 0) {
+ is->config_index = prev_id;
+ return ret;
+ }
+ }
+ is->config_index = prev_id;
+
+ set_bit(IS_ST_INIT_DONE, &is->state);
+ dev_info(dev, "initialization sequence completed (%d)\n",
+ is->config_index);
+ return 0;
+}
+
+static int fimc_is_show(struct seq_file *s, void *data)
+{
+ struct fimc_is *is = s->private;
+ const u8 *buf = is->memory.vaddr + FIMC_IS_DEBUG_REGION_OFFSET;
+
+ if (is->memory.vaddr == NULL) {
+ dev_err(&is->pdev->dev, "firmware memory is not initialized\n");
+ return -EIO;
+ }
+
+ seq_printf(s, "%s\n", buf);
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(fimc_is);
+
+static void fimc_is_debugfs_remove(struct fimc_is *is)
+{
+ debugfs_remove_recursive(is->debugfs_entry);
+ is->debugfs_entry = NULL;
+}
+
+static void fimc_is_debugfs_create(struct fimc_is *is)
+{
+ is->debugfs_entry = debugfs_create_dir("fimc_is", NULL);
+
+ debugfs_create_file("fw_log", S_IRUGO, is->debugfs_entry, is,
+ &fimc_is_fops);
+}
+
+static int fimc_is_runtime_resume(struct device *dev);
+static int fimc_is_runtime_suspend(struct device *dev);
+
+static int fimc_is_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fimc_is *is;
+ struct resource res;
+ struct device_node *node;
+ int ret;
+
+ is = devm_kzalloc(&pdev->dev, sizeof(*is), GFP_KERNEL);
+ if (!is)
+ return -ENOMEM;
+
+ is->pdev = pdev;
+ is->isp.pdev = pdev;
+
+ init_waitqueue_head(&is->irq_queue);
+ spin_lock_init(&is->slock);
+ mutex_init(&is->lock);
+
+ ret = of_address_to_resource(dev->of_node, 0, &res);
+ if (ret < 0)
+ return ret;
+
+ is->regs = devm_ioremap_resource(dev, &res);
+ if (IS_ERR(is->regs))
+ return PTR_ERR(is->regs);
+
+ node = of_get_child_by_name(dev->of_node, "pmu");
+ if (!node)
+ return -ENODEV;
+
+ is->pmu_regs = of_iomap(node, 0);
+ of_node_put(node);
+ if (!is->pmu_regs)
+ return -ENOMEM;
+
+ is->irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (!is->irq) {
+ dev_err(dev, "no irq found\n");
+ ret = -EINVAL;
+ goto err_iounmap;
+ }
+
+ ret = fimc_is_get_clocks(is);
+ if (ret < 0)
+ goto err_iounmap;
+
+ platform_set_drvdata(pdev, is);
+
+ ret = request_irq(is->irq, fimc_is_irq_handler, 0, dev_name(dev), is);
+ if (ret < 0) {
+ dev_err(dev, "irq request failed\n");
+ goto err_clk;
+ }
+ pm_runtime_enable(dev);
+
+ if (!pm_runtime_enabled(dev)) {
+ ret = fimc_is_runtime_resume(dev);
+ if (ret < 0)
+ goto err_irq;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ goto err_pm_disable;
+
+ vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ ret = devm_of_platform_populate(dev);
+ if (ret < 0)
+ goto err_pm;
+
+ /*
+ * Register FIMC-IS V4L2 subdevs to this driver. The video nodes
+ * will be created within the subdev's registered() callback.
+ */
+ ret = fimc_is_register_subdevs(is);
+ if (ret < 0)
+ goto err_pm;
+
+ fimc_is_debugfs_create(is);
+
+ ret = fimc_is_request_firmware(is, FIMC_IS_FW_FILENAME);
+ if (ret < 0)
+ goto err_dfs;
+
+ pm_runtime_put_sync(dev);
+
+ dev_dbg(dev, "FIMC-IS registered successfully\n");
+ return 0;
+
+err_dfs:
+ fimc_is_debugfs_remove(is);
+ fimc_is_unregister_subdevs(is);
+err_pm:
+ pm_runtime_put_noidle(dev);
+ if (!pm_runtime_enabled(dev))
+ fimc_is_runtime_suspend(dev);
+err_pm_disable:
+ pm_runtime_disable(dev);
+err_irq:
+ free_irq(is->irq, is);
+err_clk:
+ fimc_is_put_clocks(is);
+err_iounmap:
+ iounmap(is->pmu_regs);
+ return ret;
+}
+
+static int fimc_is_runtime_resume(struct device *dev)
+{
+ struct fimc_is *is = dev_get_drvdata(dev);
+ int ret;
+
+ ret = fimc_is_setup_clocks(is);
+ if (ret)
+ return ret;
+
+ return fimc_is_enable_clocks(is);
+}
+
+static int fimc_is_runtime_suspend(struct device *dev)
+{
+ struct fimc_is *is = dev_get_drvdata(dev);
+
+ fimc_is_disable_clocks(is);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fimc_is_resume(struct device *dev)
+{
+ /* TODO: */
+ return 0;
+}
+
+static int fimc_is_suspend(struct device *dev)
+{
+ struct fimc_is *is = dev_get_drvdata(dev);
+
+ /* TODO: */
+ if (test_bit(IS_ST_A5_PWR_ON, &is->state))
+ return -EBUSY;
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static void fimc_is_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fimc_is *is = dev_get_drvdata(dev);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ if (!pm_runtime_status_suspended(dev))
+ fimc_is_runtime_suspend(dev);
+ free_irq(is->irq, is);
+ fimc_is_unregister_subdevs(is);
+ vb2_dma_contig_clear_max_seg_size(dev);
+ fimc_is_put_clocks(is);
+ iounmap(is->pmu_regs);
+ fimc_is_debugfs_remove(is);
+ release_firmware(is->fw.f_w);
+ fimc_is_free_cpu_memory(is);
+}
+
+static const struct of_device_id fimc_is_of_match[] = {
+ { .compatible = "samsung,exynos4212-fimc-is" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, fimc_is_of_match);
+
+static const struct dev_pm_ops fimc_is_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fimc_is_suspend, fimc_is_resume)
+ SET_RUNTIME_PM_OPS(fimc_is_runtime_suspend, fimc_is_runtime_resume,
+ NULL)
+};
+
+static struct platform_driver fimc_is_driver = {
+ .probe = fimc_is_probe,
+ .remove_new = fimc_is_remove,
+ .driver = {
+ .of_match_table = fimc_is_of_match,
+ .name = FIMC_IS_DRV_NAME,
+ .pm = &fimc_is_pm_ops,
+ }
+};
+
+static int fimc_is_module_init(void)
+{
+ int ret;
+
+ ret = fimc_is_register_i2c_driver();
+ if (ret < 0)
+ return ret;
+
+ ret = platform_driver_register(&fimc_is_driver);
+
+ if (ret < 0)
+ fimc_is_unregister_i2c_driver();
+
+ return ret;
+}
+
+static void fimc_is_module_exit(void)
+{
+ fimc_is_unregister_i2c_driver();
+ platform_driver_unregister(&fimc_is_driver);
+}
+
+module_init(fimc_is_module_init);
+module_exit(fimc_is_module_exit);
+
+MODULE_ALIAS("platform:" FIMC_IS_DRV_NAME);
+MODULE_AUTHOR("Younghwan Joo <yhwan.joo@samsung.com>");
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.h b/drivers/media/platform/samsung/exynos4-is/fimc-is.h
new file mode 100644
index 0000000000..c126b779aa
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.h
@@ -0,0 +1,356 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Younghwan Joo <yhwan.joo@samsung.com>
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#ifndef FIMC_IS_H_
+#define FIMC_IS_H_
+
+#include <asm/barrier.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+
+#include "fimc-isp.h"
+#include "fimc-is-command.h"
+#include "fimc-is-sensor.h"
+#include "fimc-is-param.h"
+#include "fimc-is-regs.h"
+
+#define FIMC_IS_DRV_NAME "exynos4-fimc-is"
+
+#define FIMC_IS_FW_FILENAME "exynos4_fimc_is_fw.bin"
+#define FIMC_IS_SETFILE_6A3 "exynos4_s5k6a3_setfile.bin"
+
+#define FIMC_IS_FW_LOAD_TIMEOUT 1000 /* ms */
+#define FIMC_IS_POWER_ON_TIMEOUT 1000 /* us */
+
+#define FIMC_IS_SENSORS_NUM 2
+
+/* Memory definitions */
+#define FIMC_IS_CPU_MEM_SIZE (0xa00000)
+#define FIMC_IS_CPU_BASE_MASK ((1 << 26) - 1)
+#define FIMC_IS_REGION_SIZE 0x5000
+
+#define FIMC_IS_DEBUG_REGION_OFFSET 0x0084b000
+#define FIMC_IS_SHARED_REGION_OFFSET 0x008c0000
+#define FIMC_IS_FW_INFO_LEN 31
+#define FIMC_IS_FW_VER_LEN 7
+#define FIMC_IS_FW_DESC_LEN (FIMC_IS_FW_INFO_LEN + \
+ FIMC_IS_FW_VER_LEN)
+#define FIMC_IS_SETFILE_INFO_LEN 39
+
+#define FIMC_IS_EXTRA_MEM_SIZE (FIMC_IS_EXTRA_FW_SIZE + \
+ FIMC_IS_EXTRA_SETFILE_SIZE + 0x1000)
+#define FIMC_IS_EXTRA_FW_SIZE 0x180000
+#define FIMC_IS_EXTRA_SETFILE_SIZE 0x4b000
+
+/* TODO: revisit */
+#define FIMC_IS_FW_ADDR_MASK ((1 << 26) - 1)
+#define FIMC_IS_FW_SIZE_MAX (SZ_4M)
+#define FIMC_IS_FW_SIZE_MIN (SZ_32K)
+
+#define ATCLK_MCUISP_FREQUENCY 100000000UL
+#define ACLK_AXI_FREQUENCY 100000000UL
+
+enum {
+ ISS_CLK_PPMUISPX,
+ ISS_CLK_PPMUISPMX,
+ ISS_CLK_LITE0,
+ ISS_CLK_LITE1,
+ ISS_CLK_MPLL,
+ ISS_CLK_ISP,
+ ISS_CLK_DRC,
+ ISS_CLK_FD,
+ ISS_CLK_MCUISP,
+ ISS_CLK_GICISP,
+ ISS_CLK_PWM_ISP,
+ ISS_CLK_MCUCTL_ISP,
+ ISS_CLK_UART,
+ ISS_GATE_CLKS_MAX,
+ ISS_CLK_ISP_DIV0 = ISS_GATE_CLKS_MAX,
+ ISS_CLK_ISP_DIV1,
+ ISS_CLK_MCUISP_DIV0,
+ ISS_CLK_MCUISP_DIV1,
+ ISS_CLK_ACLK200,
+ ISS_CLK_ACLK200_DIV,
+ ISS_CLK_ACLK400MCUISP,
+ ISS_CLK_ACLK400MCUISP_DIV,
+ ISS_CLKS_MAX
+};
+
+/* The driver's internal state flags */
+enum {
+ IS_ST_IDLE,
+ IS_ST_PWR_ON,
+ IS_ST_A5_PWR_ON,
+ IS_ST_FW_LOADED,
+ IS_ST_OPEN_SENSOR,
+ IS_ST_SETFILE_LOADED,
+ IS_ST_INIT_DONE,
+ IS_ST_STREAM_ON,
+ IS_ST_STREAM_OFF,
+ IS_ST_CHANGE_MODE,
+ IS_ST_BLOCK_CMD_CLEARED,
+ IS_ST_SET_ZOOM,
+ IS_ST_PWR_SUBIP_ON,
+ IS_ST_END,
+};
+
+enum af_state {
+ FIMC_IS_AF_IDLE = 0,
+ FIMC_IS_AF_SETCONFIG = 1,
+ FIMC_IS_AF_RUNNING = 2,
+ FIMC_IS_AF_LOCK = 3,
+ FIMC_IS_AF_ABORT = 4,
+ FIMC_IS_AF_FAILED = 5,
+};
+
+enum af_lock_state {
+ FIMC_IS_AF_UNLOCKED = 0,
+ FIMC_IS_AF_LOCKED = 2
+};
+
+enum ae_lock_state {
+ FIMC_IS_AE_UNLOCKED = 0,
+ FIMC_IS_AE_LOCKED = 1
+};
+
+enum awb_lock_state {
+ FIMC_IS_AWB_UNLOCKED = 0,
+ FIMC_IS_AWB_LOCKED = 1
+};
+
+enum {
+ IS_METERING_CONFIG_CMD,
+ IS_METERING_CONFIG_WIN_POS_X,
+ IS_METERING_CONFIG_WIN_POS_Y,
+ IS_METERING_CONFIG_WIN_WIDTH,
+ IS_METERING_CONFIG_WIN_HEIGHT,
+ IS_METERING_CONFIG_MAX
+};
+
+struct is_setfile {
+ const struct firmware *info;
+ int state;
+ u32 sub_index;
+ u32 base;
+ size_t size;
+};
+
+struct is_fd_result_header {
+ u32 offset;
+ u32 count;
+ u32 index;
+ u32 curr_index;
+ u32 width;
+ u32 height;
+};
+
+struct is_af_info {
+ u16 mode;
+ u32 af_state;
+ u32 af_lock_state;
+ u32 ae_lock_state;
+ u32 awb_lock_state;
+ u16 pos_x;
+ u16 pos_y;
+ u16 prev_pos_x;
+ u16 prev_pos_y;
+ u16 use_af;
+};
+
+struct fimc_is_firmware {
+ const struct firmware *f_w;
+
+ dma_addr_t addr;
+ void *vaddr;
+ unsigned int size;
+
+ char info[FIMC_IS_FW_INFO_LEN + 1];
+ char version[FIMC_IS_FW_VER_LEN + 1];
+ char setfile_info[FIMC_IS_SETFILE_INFO_LEN + 1];
+ u8 state;
+};
+
+struct fimc_is_memory {
+ /* DMA base address */
+ dma_addr_t addr;
+ /* virtual base address */
+ void *vaddr;
+ /* total length */
+ unsigned int size;
+};
+
+#define FIMC_IS_I2H_MAX_ARGS 12
+
+struct i2h_cmd {
+ u32 cmd;
+ u32 sensor_id;
+ u16 num_args;
+ u32 args[FIMC_IS_I2H_MAX_ARGS];
+};
+
+struct h2i_cmd {
+ u16 cmd_type;
+ u32 entry_id;
+};
+
+#define FIMC_IS_DEBUG_MSG 0x3f
+#define FIMC_IS_DEBUG_LEVEL 3
+
+struct fimc_is_setfile {
+ const struct firmware *info;
+ unsigned int state;
+ unsigned int size;
+ u32 sub_index;
+ u32 base;
+};
+
+struct chain_config {
+ struct global_param global;
+ struct sensor_param sensor;
+ struct isp_param isp;
+ struct drc_param drc;
+ struct fd_param fd;
+
+ unsigned long p_region_index[2];
+};
+
+/**
+ * struct fimc_is - fimc-is data structure
+ * @pdev: pointer to FIMC-IS platform device
+ * @v4l2_dev: pointer to the top level v4l2_device
+ * @fw: data structure describing the FIMC-IS firmware binary
+ * @memory: memory region assigned for the FIMC-IS (firmware)
+ * @isp: the ISP block data structure
+ * @sensor: fimc-is sensor subdevice array
+ * @setfile: descriptor of the imaging pipeline calibration data
+ * @ctrl_handler: the v4l2 controls handler
+ * @lock: mutex serializing video device and the subdev operations
+ * @slock: spinlock protecting this data structure and the hw registers
+ * @clocks: FIMC-LITE gate clock
+ * @regs: MCUCTL mmapped registers region
+ * @pmu_regs: PMU ISP mmapped registers region
+ * @irq: FIMC-IS interrupt
+ * @irq_queue: interrupt handling waitqueue
+ * @lpm: low power mode flag
+ * @state: internal driver's state flags
+ * @sensor_index: image sensor index for the firmware
+ * @i2h_cmd: FIMC-IS to the host (CPU) mailbox command data structure
+ * @h2i_cmd: the host (CPU) to FIMC-IS mailbox command data structure
+ * @fd_header: the face detection result data structure
+ * @config: shared HW pipeline configuration data
+ * @config_index: index to the @config entry currently in use
+ * @is_p_region: pointer to the shared parameter memory region
+ * @is_dma_p_region: DMA address of the shared parameter memory region
+ * @is_shared_region: pointer to the IS shared region data structure
+ * @af: auto focus data
+ * @debugfs_entry: debugfs entry for the firmware log
+ */
+struct fimc_is {
+ struct platform_device *pdev;
+ struct v4l2_device *v4l2_dev;
+
+ struct fimc_is_firmware fw;
+ struct fimc_is_memory memory;
+
+ struct fimc_isp isp;
+ struct fimc_is_sensor sensor[FIMC_IS_SENSORS_NUM];
+ struct fimc_is_setfile setfile;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ struct clk *clocks[ISS_CLKS_MAX];
+ void __iomem *regs;
+ void __iomem *pmu_regs;
+ int irq;
+ wait_queue_head_t irq_queue;
+ u8 lpm;
+
+ unsigned long state;
+ unsigned int sensor_index;
+
+ struct i2h_cmd i2h_cmd;
+ struct h2i_cmd h2i_cmd;
+ struct is_fd_result_header fd_header;
+
+ struct chain_config config[IS_SC_MAX];
+ unsigned config_index;
+
+ struct is_region *is_p_region;
+ dma_addr_t is_dma_p_region;
+ struct is_share_region *is_shared_region;
+ struct is_af_info af;
+
+ struct dentry *debugfs_entry;
+};
+
+static inline struct fimc_is *fimc_isp_to_is(struct fimc_isp *isp)
+{
+ return container_of(isp, struct fimc_is, isp);
+}
+
+static inline struct chain_config *__get_curr_is_config(struct fimc_is *is)
+{
+ return &is->config[is->config_index];
+}
+
+static inline void fimc_is_mem_barrier(void)
+{
+ mb();
+}
+
+static inline void fimc_is_set_param_bit(struct fimc_is *is, int num)
+{
+ struct chain_config *cfg = &is->config[is->config_index];
+
+ set_bit(num, &cfg->p_region_index[0]);
+}
+
+static inline void fimc_is_set_param_ctrl_cmd(struct fimc_is *is, int cmd)
+{
+ is->is_p_region->parameter.isp.control.cmd = cmd;
+}
+
+static inline void mcuctl_write(u32 v, struct fimc_is *is, unsigned int offset)
+{
+ writel(v, is->regs + offset);
+}
+
+static inline u32 mcuctl_read(struct fimc_is *is, unsigned int offset)
+{
+ return readl(is->regs + offset);
+}
+
+static inline void pmuisp_write(u32 v, struct fimc_is *is, unsigned int offset)
+{
+ writel(v, is->pmu_regs + offset);
+}
+
+static inline u32 pmuisp_read(struct fimc_is *is, unsigned int offset)
+{
+ return readl(is->pmu_regs + offset);
+}
+
+int fimc_is_wait_event(struct fimc_is *is, unsigned long bit,
+ unsigned int state, unsigned int timeout);
+int fimc_is_cpu_set_power(struct fimc_is *is, int on);
+int fimc_is_start_firmware(struct fimc_is *is);
+int fimc_is_hw_initialize(struct fimc_is *is);
+void fimc_is_log_dump(const char *level, const void *buf, size_t len);
+
+#endif /* FIMC_IS_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c
new file mode 100644
index 0000000000..8fa26969c4
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c
@@ -0,0 +1,659 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * FIMC-IS ISP video input and video output DMA interface driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * The hardware handling code derived from a driver written by
+ * Younghwan Joo <yhwan.joo@samsung.com>.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/printk.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#include "common.h"
+#include "media-dev.h"
+#include "fimc-is.h"
+#include "fimc-isp-video.h"
+#include "fimc-is-param.h"
+
+static int isp_video_capture_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct fimc_isp *isp = vb2_get_drv_priv(vq);
+ struct v4l2_pix_format_mplane *vid_fmt = &isp->video_capture.pixfmt;
+ const struct fimc_fmt *fmt = isp->video_capture.format;
+ unsigned int wh, i;
+
+ wh = vid_fmt->width * vid_fmt->height;
+
+ if (fmt == NULL)
+ return -EINVAL;
+
+ *num_buffers = clamp_t(u32, *num_buffers, FIMC_ISP_REQ_BUFS_MIN,
+ FIMC_ISP_REQ_BUFS_MAX);
+ if (*num_planes) {
+ if (*num_planes != fmt->memplanes)
+ return -EINVAL;
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < (wh * fmt->depth[i]) / 8)
+ return -EINVAL;
+ return 0;
+ }
+
+ *num_planes = fmt->memplanes;
+
+ for (i = 0; i < fmt->memplanes; i++)
+ sizes[i] = (wh * fmt->depth[i]) / 8;
+
+ return 0;
+}
+
+static inline struct param_dma_output *__get_isp_dma2(struct fimc_is *is)
+{
+ return &__get_curr_is_config(is)->isp.dma2_output;
+}
+
+static int isp_video_capture_start_streaming(struct vb2_queue *q,
+ unsigned int count)
+{
+ struct fimc_isp *isp = vb2_get_drv_priv(q);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ struct param_dma_output *dma = __get_isp_dma2(is);
+ struct fimc_is_video *video = &isp->video_capture;
+ int ret;
+
+ if (!test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state) ||
+ test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state))
+ return 0;
+
+
+ dma->cmd = DMA_OUTPUT_COMMAND_ENABLE;
+ dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_ENABLE;
+ dma->buffer_address = is->is_dma_p_region +
+ DMA2_OUTPUT_ADDR_ARRAY_OFFS;
+ dma->buffer_number = video->reqbufs_count;
+ dma->dma_out_mask = video->buf_mask;
+
+ isp_dbg(2, &video->ve.vdev,
+ "buf_count: %d, planes: %d, dma addr table: %#x\n",
+ video->buf_count, video->format->memplanes,
+ dma->buffer_address);
+
+ fimc_is_mem_barrier();
+
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT);
+ __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT);
+
+ ret = fimc_is_itf_s_param(is, false);
+ if (ret < 0)
+ return ret;
+
+ ret = fimc_pipeline_call(&video->ve, set_stream, 1);
+ if (ret < 0)
+ return ret;
+
+ set_bit(ST_ISP_VID_CAP_STREAMING, &isp->state);
+ return ret;
+}
+
+static void isp_video_capture_stop_streaming(struct vb2_queue *q)
+{
+ struct fimc_isp *isp = vb2_get_drv_priv(q);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ struct param_dma_output *dma = __get_isp_dma2(is);
+ int ret;
+
+ ret = fimc_pipeline_call(&isp->video_capture.ve, set_stream, 0);
+ if (ret < 0)
+ return;
+
+ dma->cmd = DMA_OUTPUT_COMMAND_DISABLE;
+ dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_DISABLE;
+ dma->buffer_number = 0;
+ dma->buffer_address = 0;
+ dma->dma_out_mask = 0;
+
+ fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT);
+ __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT);
+
+ ret = fimc_is_itf_s_param(is, false);
+ if (ret < 0)
+ dev_warn(&is->pdev->dev, "%s: DMA stop failed\n", __func__);
+
+ fimc_is_hw_set_isp_buf_mask(is, 0);
+
+ clear_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state);
+ clear_bit(ST_ISP_VID_CAP_STREAMING, &isp->state);
+
+ isp->video_capture.buf_count = 0;
+}
+
+static int isp_video_capture_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+ struct fimc_is_video *video = &isp->video_capture;
+ int i;
+
+ if (video->format == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < video->format->memplanes; i++) {
+ unsigned long size = video->pixfmt.plane_fmt[i].sizeimage;
+
+ if (vb2_plane_size(vb, i) < size) {
+ v4l2_err(&video->ve.vdev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb, i), size);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb, i, size);
+ }
+
+ /* Check if we get one of the already known buffers. */
+ if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) {
+ dma_addr_t dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ int i;
+
+ for (i = 0; i < video->buf_count; i++)
+ if (video->buffers[i]->dma_addr[0] == dma_addr)
+ return 0;
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void isp_video_capture_buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+ struct fimc_is_video *video = &isp->video_capture;
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ struct isp_video_buf *ivb = to_isp_video_buf(vbuf);
+ unsigned long flags;
+ unsigned int i;
+
+ if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) {
+ spin_lock_irqsave(&is->slock, flags);
+ video->buf_mask |= BIT(ivb->index);
+ spin_unlock_irqrestore(&is->slock, flags);
+ } else {
+ unsigned int num_planes = video->format->memplanes;
+
+ ivb->index = video->buf_count;
+ video->buffers[ivb->index] = ivb;
+
+ for (i = 0; i < num_planes; i++) {
+ int buf_index = ivb->index * num_planes + i;
+
+ ivb->dma_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i);
+ is->is_p_region->shared[32 + buf_index] =
+ ivb->dma_addr[i];
+
+ isp_dbg(2, &video->ve.vdev,
+ "dma_buf %d (%d/%d/%d) addr: %pad\n",
+ buf_index, ivb->index, i, vb->index,
+ &ivb->dma_addr[i]);
+ }
+
+ if (++video->buf_count < video->reqbufs_count)
+ return;
+
+ video->buf_mask = (1UL << video->buf_count) - 1;
+ set_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state);
+ }
+
+ if (!test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state))
+ isp_video_capture_start_streaming(vb->vb2_queue, 0);
+}
+
+/*
+ * FIMC-IS ISP input and output DMA interface interrupt handler.
+ * Locking: called with is->slock spinlock held.
+ */
+void fimc_isp_video_irq_handler(struct fimc_is *is)
+{
+ struct fimc_is_video *video = &is->isp.video_capture;
+ struct vb2_v4l2_buffer *vbuf;
+ int buf_index;
+
+ /* TODO: Ensure the DMA is really stopped in stop_streaming callback */
+ if (!test_bit(ST_ISP_VID_CAP_STREAMING, &is->isp.state))
+ return;
+
+ buf_index = (is->i2h_cmd.args[1] - 1) % video->buf_count;
+ vbuf = &video->buffers[buf_index]->vb;
+
+ vbuf->vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE);
+
+ video->buf_mask &= ~BIT(buf_index);
+ fimc_is_hw_set_isp_buf_mask(is, video->buf_mask);
+}
+
+static const struct vb2_ops isp_video_capture_qops = {
+ .queue_setup = isp_video_capture_queue_setup,
+ .buf_prepare = isp_video_capture_buffer_prepare,
+ .buf_queue = isp_video_capture_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = isp_video_capture_start_streaming,
+ .stop_streaming = isp_video_capture_stop_streaming,
+};
+
+static int isp_video_open(struct file *file)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ struct exynos_video_entity *ve = &isp->video_capture.ve;
+ struct media_entity *me = &ve->vdev.entity;
+ int ret;
+
+ if (mutex_lock_interruptible(&isp->video_lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = pm_runtime_resume_and_get(&isp->pdev->dev);
+ if (ret < 0)
+ goto rel_fh;
+
+ if (v4l2_fh_is_singular_file(file)) {
+ mutex_lock(&me->graph_obj.mdev->graph_mutex);
+
+ ret = fimc_pipeline_call(ve, open, me, true);
+
+ /* Mark the video pipeline as in use. */
+ if (ret == 0)
+ me->use_count++;
+
+ mutex_unlock(&me->graph_obj.mdev->graph_mutex);
+ }
+ if (!ret)
+ goto unlock;
+rel_fh:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&isp->video_lock);
+ return ret;
+}
+
+static int isp_video_release(struct file *file)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ struct fimc_is_video *ivc = &isp->video_capture;
+ struct media_entity *entity = &ivc->ve.vdev.entity;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ bool is_singular_file;
+
+ mutex_lock(&isp->video_lock);
+
+ is_singular_file = v4l2_fh_is_singular_file(file);
+
+ if (is_singular_file && ivc->streaming) {
+ video_device_pipeline_stop(&ivc->ve.vdev);
+ ivc->streaming = 0;
+ }
+
+ _vb2_fop_release(file, NULL);
+
+ if (is_singular_file) {
+ fimc_pipeline_call(&ivc->ve, close);
+
+ mutex_lock(&mdev->graph_mutex);
+ entity->use_count--;
+ mutex_unlock(&mdev->graph_mutex);
+ }
+
+ pm_runtime_put(&isp->pdev->dev);
+ mutex_unlock(&isp->video_lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations isp_video_fops = {
+ .owner = THIS_MODULE,
+ .open = isp_video_open,
+ .release = isp_video_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * Video node ioctl operations
+ */
+static int isp_video_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+
+ __fimc_vidioc_querycap(&isp->pdev->dev, cap);
+ return 0;
+}
+
+static int isp_video_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ const struct fimc_fmt *fmt;
+
+ if (f->index >= FIMC_ISP_NUM_FORMATS)
+ return -EINVAL;
+
+ fmt = fimc_isp_find_format(NULL, NULL, f->index);
+ if (WARN_ON(fmt == NULL))
+ return -EINVAL;
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int isp_video_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+
+ f->fmt.pix_mp = isp->video_capture.pixfmt;
+ return 0;
+}
+
+static void __isp_video_try_fmt(struct fimc_isp *isp,
+ struct v4l2_pix_format_mplane *pixm,
+ const struct fimc_fmt **fmt)
+{
+ const struct fimc_fmt *__fmt;
+
+ __fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, 2);
+
+ if (fmt)
+ *fmt = __fmt;
+
+ pixm->colorspace = V4L2_COLORSPACE_SRGB;
+ pixm->field = V4L2_FIELD_NONE;
+ pixm->num_planes = __fmt->memplanes;
+ pixm->pixelformat = __fmt->fourcc;
+ /*
+ * TODO: double check with the docmentation these width/height
+ * constraints are correct.
+ */
+ v4l_bound_align_image(&pixm->width, FIMC_ISP_SOURCE_WIDTH_MIN,
+ FIMC_ISP_SOURCE_WIDTH_MAX, 3,
+ &pixm->height, FIMC_ISP_SOURCE_HEIGHT_MIN,
+ FIMC_ISP_SOURCE_HEIGHT_MAX, 0, 0);
+}
+
+static int isp_video_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+
+ __isp_video_try_fmt(isp, &f->fmt.pix_mp, NULL);
+ return 0;
+}
+
+static int isp_video_s_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
+ const struct fimc_fmt *ifmt = NULL;
+ struct param_dma_output *dma = __get_isp_dma2(is);
+
+ __isp_video_try_fmt(isp, pixm, &ifmt);
+
+ if (WARN_ON(ifmt == NULL))
+ return -EINVAL;
+
+ dma->format = DMA_OUTPUT_FORMAT_BAYER;
+ dma->order = DMA_OUTPUT_ORDER_GB_BG;
+ dma->plane = ifmt->memplanes;
+ dma->bitwidth = ifmt->depth[0];
+ dma->width = pixm->width;
+ dma->height = pixm->height;
+
+ fimc_is_mem_barrier();
+
+ isp->video_capture.format = ifmt;
+ isp->video_capture.pixfmt = *pixm;
+
+ return 0;
+}
+
+/*
+ * Check for source/sink format differences at each link.
+ * Return 0 if the formats match or -EPIPE otherwise.
+ */
+static int isp_video_pipeline_validate(struct fimc_isp *isp)
+{
+ struct v4l2_subdev *sd = &isp->subdev;
+ struct media_pad *pad;
+ int ret;
+
+ while (1) {
+ struct v4l2_subdev_format sink_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_subdev_format src_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ /* Retrieve format at the sink pad */
+ pad = &sd->entity.pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+ sink_fmt.pad = pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sink_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ /* Retrieve format at the source pad */
+ pad = media_pad_remote_pad_first(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+ src_fmt.pad = pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ if (src_fmt.format.width != sink_fmt.format.width ||
+ src_fmt.format.height != sink_fmt.format.height ||
+ src_fmt.format.code != sink_fmt.format.code)
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+static int isp_video_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ struct exynos_video_entity *ve = &isp->video_capture.ve;
+ int ret;
+
+ ret = video_device_pipeline_start(&ve->vdev, &ve->pipe->mp);
+ if (ret < 0)
+ return ret;
+
+ ret = isp_video_pipeline_validate(isp);
+ if (ret < 0)
+ goto p_stop;
+
+ ret = vb2_ioctl_streamon(file, priv, type);
+ if (ret < 0)
+ goto p_stop;
+
+ isp->video_capture.streaming = 1;
+ return 0;
+p_stop:
+ video_device_pipeline_stop(&ve->vdev);
+ return ret;
+}
+
+static int isp_video_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ struct fimc_is_video *video = &isp->video_capture;
+ int ret;
+
+ ret = vb2_ioctl_streamoff(file, priv, type);
+ if (ret < 0)
+ return ret;
+
+ video_device_pipeline_stop(&video->ve.vdev);
+ video->streaming = 0;
+ return 0;
+}
+
+static int isp_video_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *rb)
+{
+ struct fimc_isp *isp = video_drvdata(file);
+ int ret;
+
+ ret = vb2_ioctl_reqbufs(file, priv, rb);
+ if (ret < 0)
+ return ret;
+
+ if (rb->count && rb->count < FIMC_ISP_REQ_BUFS_MIN) {
+ rb->count = 0;
+ vb2_ioctl_reqbufs(file, priv, rb);
+ ret = -ENOMEM;
+ }
+
+ isp->video_capture.reqbufs_count = rb->count;
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops isp_video_ioctl_ops = {
+ .vidioc_querycap = isp_video_querycap,
+ .vidioc_enum_fmt_vid_cap = isp_video_enum_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = isp_video_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = isp_video_s_fmt_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = isp_video_g_fmt_mplane,
+ .vidioc_reqbufs = isp_video_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = isp_video_streamon,
+ .vidioc_streamoff = isp_video_streamoff,
+};
+
+int fimc_isp_video_device_register(struct fimc_isp *isp,
+ struct v4l2_device *v4l2_dev,
+ enum v4l2_buf_type type)
+{
+ struct vb2_queue *q = &isp->video_capture.vb_queue;
+ struct fimc_is_video *iv;
+ struct video_device *vdev;
+ int ret;
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ iv = &isp->video_capture;
+ else
+ return -ENOSYS;
+
+ mutex_init(&isp->video_lock);
+ INIT_LIST_HEAD(&iv->pending_buf_q);
+ INIT_LIST_HEAD(&iv->active_buf_q);
+ iv->format = fimc_isp_find_format(NULL, NULL, 0);
+ iv->pixfmt.width = IS_DEFAULT_WIDTH;
+ iv->pixfmt.height = IS_DEFAULT_HEIGHT;
+ iv->pixfmt.pixelformat = iv->format->fourcc;
+ iv->pixfmt.colorspace = V4L2_COLORSPACE_SRGB;
+ iv->reqbufs_count = 0;
+
+ memset(q, 0, sizeof(*q));
+ q->type = type;
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->ops = &isp_video_capture_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct isp_video_buf);
+ q->drv_priv = isp;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &isp->video_lock;
+ q->dev = &isp->pdev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret < 0)
+ return ret;
+
+ vdev = &iv->ve.vdev;
+ memset(vdev, 0, sizeof(*vdev));
+ strscpy(vdev->name, "fimc-is-isp.capture", sizeof(vdev->name));
+ vdev->queue = q;
+ vdev->fops = &isp_video_fops;
+ vdev->ioctl_ops = &isp_video_ioctl_ops;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->minor = -1;
+ vdev->release = video_device_release_empty;
+ vdev->lock = &isp->video_lock;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+
+ iv->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vdev->entity, 1, &iv->pad);
+ if (ret < 0)
+ return ret;
+
+ video_set_drvdata(vdev, isp);
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ media_entity_cleanup(&vdev->entity);
+ return ret;
+ }
+
+ v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
+ vdev->name, video_device_node_name(vdev));
+
+ return 0;
+}
+
+void fimc_isp_video_device_unregister(struct fimc_isp *isp,
+ enum v4l2_buf_type type)
+{
+ struct exynos_video_entity *ve;
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ ve = &isp->video_capture.ve;
+ else
+ return;
+
+ mutex_lock(&isp->video_lock);
+
+ if (video_is_registered(&ve->vdev)) {
+ video_unregister_device(&ve->vdev);
+ media_entity_cleanup(&ve->vdev.entity);
+ ve->pipe = NULL;
+ }
+
+ mutex_unlock(&isp->video_lock);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h
new file mode 100644
index 0000000000..2dd4ddbc74
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#ifndef FIMC_ISP_VIDEO__
+#define FIMC_ISP_VIDEO__
+
+#include <media/videobuf2-v4l2.h>
+#include "fimc-isp.h"
+
+#ifdef CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE
+int fimc_isp_video_device_register(struct fimc_isp *isp,
+ struct v4l2_device *v4l2_dev,
+ enum v4l2_buf_type type);
+
+void fimc_isp_video_device_unregister(struct fimc_isp *isp,
+ enum v4l2_buf_type type);
+
+void fimc_isp_video_irq_handler(struct fimc_is *is);
+#else
+static inline void fimc_isp_video_irq_handler(struct fimc_is *is)
+{
+}
+
+static inline int fimc_isp_video_device_register(struct fimc_isp *isp,
+ struct v4l2_device *v4l2_dev,
+ enum v4l2_buf_type type)
+{
+ return 0;
+}
+
+static inline void fimc_isp_video_device_unregister(struct fimc_isp *isp,
+ enum v4l2_buf_type type)
+{
+}
+#endif /* !CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE */
+
+#endif /* FIMC_ISP_VIDEO__ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c
new file mode 100644
index 0000000000..b85986e50f
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c
@@ -0,0 +1,789 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ * Younghwan Joo <yhwan.joo@samsung.com>
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <media/v4l2-device.h>
+
+#include "media-dev.h"
+#include "fimc-isp-video.h"
+#include "fimc-is-command.h"
+#include "fimc-is-param.h"
+#include "fimc-is-regs.h"
+#include "fimc-is.h"
+
+int fimc_isp_debug;
+module_param_named(debug_isp, fimc_isp_debug, int, S_IRUGO | S_IWUSR);
+
+static const struct fimc_fmt fimc_isp_formats[FIMC_ISP_NUM_FORMATS] = {
+ {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .depth = { 8 },
+ .color = FIMC_FMT_RAW8,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ .depth = { 10 },
+ .color = FIMC_FMT_RAW10,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG12,
+ .depth = { 12 },
+ .color = FIMC_FMT_RAW12,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ },
+};
+
+/**
+ * fimc_isp_find_format - lookup color format by fourcc or media bus code
+ * @pixelformat: fourcc to match, ignored if null
+ * @mbus_code: media bus code to match, ignored if null
+ * @index: index to the fimc_isp_formats array, ignored if negative
+ */
+const struct fimc_fmt *fimc_isp_find_format(const u32 *pixelformat,
+ const u32 *mbus_code, int index)
+{
+ const struct fimc_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)ARRAY_SIZE(fimc_isp_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(fimc_isp_formats); ++i) {
+ fmt = &fimc_isp_formats[i];
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+void fimc_isp_irq_handler(struct fimc_is *is)
+{
+ is->i2h_cmd.args[0] = mcuctl_read(is, MCUCTL_REG_ISSR(20));
+ is->i2h_cmd.args[1] = mcuctl_read(is, MCUCTL_REG_ISSR(21));
+
+ fimc_is_fw_clear_irq1(is, FIMC_IS_INT_FRAME_DONE_ISP);
+ fimc_isp_video_irq_handler(is);
+
+ wake_up(&is->irq_queue);
+}
+
+/* Capture subdev media entity operations */
+static int fimc_is_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ return 0;
+}
+
+static const struct media_entity_operations fimc_is_subdev_media_ops = {
+ .link_setup = fimc_is_link_setup,
+};
+
+static int fimc_is_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ const struct fimc_fmt *fmt;
+
+ fmt = fimc_isp_find_format(NULL, NULL, code->index);
+ if (!fmt)
+ return -EINVAL;
+ code->code = fmt->mbus_code;
+ return 0;
+}
+
+static int fimc_isp_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *mf = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ return 0;
+ }
+
+ mf->colorspace = V4L2_COLORSPACE_SRGB;
+
+ mutex_lock(&isp->subdev_lock);
+
+ if (fmt->pad == FIMC_ISP_SD_PAD_SINK) {
+ /* ISP OTF input image format */
+ *mf = isp->sink_fmt;
+ } else {
+ /* ISP OTF output image format */
+ *mf = isp->src_fmt;
+
+ if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) {
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+ mf->code = MEDIA_BUS_FMT_YUV10_1X30;
+ }
+ }
+
+ mutex_unlock(&isp->subdev_lock);
+
+ isp_dbg(1, sd, "%s: pad%d: fmt: 0x%x, %dx%d\n", __func__,
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ return 0;
+}
+
+static void __isp_subdev_try_format(struct fimc_isp *isp,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct v4l2_mbus_framefmt *format;
+
+ mf->colorspace = V4L2_COLORSPACE_SRGB;
+
+ if (fmt->pad == FIMC_ISP_SD_PAD_SINK) {
+ v4l_bound_align_image(&mf->width, FIMC_ISP_SINK_WIDTH_MIN,
+ FIMC_ISP_SINK_WIDTH_MAX, 0,
+ &mf->height, FIMC_ISP_SINK_HEIGHT_MIN,
+ FIMC_ISP_SINK_HEIGHT_MAX, 0, 0);
+ mf->code = MEDIA_BUS_FMT_SGRBG10_1X10;
+ } else {
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ format = v4l2_subdev_get_try_format(&isp->subdev,
+ sd_state,
+ FIMC_ISP_SD_PAD_SINK);
+ else
+ format = &isp->sink_fmt;
+
+ /* Allow changing format only on sink pad */
+ mf->width = format->width - FIMC_ISP_CAC_MARGIN_WIDTH;
+ mf->height = format->height - FIMC_ISP_CAC_MARGIN_HEIGHT;
+
+ if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) {
+ mf->code = MEDIA_BUS_FMT_YUV10_1X30;
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+ } else {
+ mf->code = format->code;
+ }
+ }
+}
+
+static int fimc_isp_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ int ret = 0;
+
+ isp_dbg(1, sd, "%s: pad%d: code: 0x%x, %dx%d\n",
+ __func__, fmt->pad, mf->code, mf->width, mf->height);
+
+ mutex_lock(&isp->subdev_lock);
+ __isp_subdev_try_format(isp, sd_state, fmt);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ *mf = fmt->format;
+
+ /* Propagate format to the source pads */
+ if (fmt->pad == FIMC_ISP_SD_PAD_SINK) {
+ struct v4l2_subdev_format format = *fmt;
+ unsigned int pad;
+
+ for (pad = FIMC_ISP_SD_PAD_SRC_FIFO;
+ pad < FIMC_ISP_SD_PADS_NUM; pad++) {
+ format.pad = pad;
+ __isp_subdev_try_format(isp, sd_state,
+ &format);
+ mf = v4l2_subdev_get_try_format(sd, sd_state,
+ pad);
+ *mf = format.format;
+ }
+ }
+ } else {
+ if (!media_entity_is_streaming(&sd->entity)) {
+ if (fmt->pad == FIMC_ISP_SD_PAD_SINK) {
+ struct v4l2_subdev_format format = *fmt;
+
+ isp->sink_fmt = *mf;
+
+ format.pad = FIMC_ISP_SD_PAD_SRC_DMA;
+ __isp_subdev_try_format(isp, sd_state,
+ &format);
+
+ isp->src_fmt = format.format;
+ __is_set_frame_size(is, &isp->src_fmt);
+ } else {
+ isp->src_fmt = *mf;
+ }
+ } else {
+ ret = -EBUSY;
+ }
+ }
+
+ mutex_unlock(&isp->subdev_lock);
+ return ret;
+}
+
+static int fimc_isp_subdev_s_stream(struct v4l2_subdev *sd, int on)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ int ret;
+
+ isp_dbg(1, sd, "%s: on: %d\n", __func__, on);
+
+ if (!test_bit(IS_ST_INIT_DONE, &is->state))
+ return -EBUSY;
+
+ fimc_is_mem_barrier();
+
+ if (on) {
+ if (__get_pending_param_count(is)) {
+ ret = fimc_is_itf_s_param(is, true);
+ if (ret < 0)
+ return ret;
+ }
+
+ isp_dbg(1, sd, "changing mode to %d\n", is->config_index);
+
+ ret = fimc_is_itf_mode_change(is);
+ if (ret)
+ return -EINVAL;
+
+ clear_bit(IS_ST_STREAM_ON, &is->state);
+ fimc_is_hw_stream_on(is);
+ ret = fimc_is_wait_event(is, IS_ST_STREAM_ON, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ v4l2_err(sd, "stream on timeout\n");
+ return ret;
+ }
+ } else {
+ clear_bit(IS_ST_STREAM_OFF, &is->state);
+ fimc_is_hw_stream_off(is);
+ ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ v4l2_err(sd, "stream off timeout\n");
+ return ret;
+ }
+ is->setfile.sub_index = 0;
+ }
+
+ return 0;
+}
+
+static int fimc_isp_subdev_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ int ret = 0;
+
+ pr_debug("on: %d\n", on);
+
+ if (on) {
+ ret = pm_runtime_resume_and_get(&is->pdev->dev);
+ if (ret < 0)
+ return ret;
+
+ set_bit(IS_ST_PWR_ON, &is->state);
+
+ ret = fimc_is_start_firmware(is);
+ if (ret < 0) {
+ v4l2_err(sd, "firmware booting failed\n");
+ pm_runtime_put(&is->pdev->dev);
+ return ret;
+ }
+ set_bit(IS_ST_PWR_SUBIP_ON, &is->state);
+
+ ret = fimc_is_hw_initialize(is);
+ } else {
+ /* Close sensor */
+ if (!test_bit(IS_ST_PWR_ON, &is->state)) {
+ fimc_is_hw_close_sensor(is, 0);
+
+ ret = fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 0,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ v4l2_err(sd, "sensor close timeout\n");
+ return ret;
+ }
+ }
+
+ /* SUB IP power off */
+ if (test_bit(IS_ST_PWR_SUBIP_ON, &is->state)) {
+ fimc_is_hw_subip_power_off(is);
+ ret = fimc_is_wait_event(is, IS_ST_PWR_SUBIP_ON, 0,
+ FIMC_IS_CONFIG_TIMEOUT);
+ if (ret < 0) {
+ v4l2_err(sd, "sub-IP power off timeout\n");
+ return ret;
+ }
+ }
+
+ fimc_is_cpu_set_power(is, 0);
+ pm_runtime_put_sync(&is->pdev->dev);
+
+ clear_bit(IS_ST_PWR_ON, &is->state);
+ clear_bit(IS_ST_INIT_DONE, &is->state);
+ is->state = 0;
+ is->config[is->config_index].p_region_index[0] = 0;
+ is->config[is->config_index].p_region_index[1] = 0;
+ set_bit(IS_ST_IDLE, &is->state);
+ wmb();
+ }
+
+ return ret;
+}
+
+static int fimc_isp_subdev_open(struct v4l2_subdev *sd,
+ struct v4l2_subdev_fh *fh)
+{
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_mbus_framefmt fmt = {
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .code = fimc_isp_formats[0].mbus_code,
+ .width = DEFAULT_PREVIEW_STILL_WIDTH + FIMC_ISP_CAC_MARGIN_WIDTH,
+ .height = DEFAULT_PREVIEW_STILL_HEIGHT + FIMC_ISP_CAC_MARGIN_HEIGHT,
+ .field = V4L2_FIELD_NONE,
+ };
+
+ format = v4l2_subdev_get_try_format(sd, fh->state,
+ FIMC_ISP_SD_PAD_SINK);
+ *format = fmt;
+
+ format = v4l2_subdev_get_try_format(sd, fh->state,
+ FIMC_ISP_SD_PAD_SRC_FIFO);
+ fmt.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ *format = fmt;
+
+ format = v4l2_subdev_get_try_format(sd, fh->state,
+ FIMC_ISP_SD_PAD_SRC_DMA);
+ *format = fmt;
+
+ return 0;
+}
+
+static int fimc_isp_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+ int ret;
+
+ /* Use pipeline object allocated by the media device. */
+ isp->video_capture.ve.pipe = v4l2_get_subdev_hostdata(sd);
+
+ ret = fimc_isp_video_device_register(isp, sd->v4l2_dev,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+ if (ret < 0)
+ isp->video_capture.ve.pipe = NULL;
+
+ return ret;
+}
+
+static void fimc_isp_subdev_unregistered(struct v4l2_subdev *sd)
+{
+ struct fimc_isp *isp = v4l2_get_subdevdata(sd);
+
+ fimc_isp_video_device_unregister(isp,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+}
+
+static const struct v4l2_subdev_internal_ops fimc_is_subdev_internal_ops = {
+ .registered = fimc_isp_subdev_registered,
+ .unregistered = fimc_isp_subdev_unregistered,
+ .open = fimc_isp_subdev_open,
+};
+
+static const struct v4l2_subdev_pad_ops fimc_is_subdev_pad_ops = {
+ .enum_mbus_code = fimc_is_subdev_enum_mbus_code,
+ .get_fmt = fimc_isp_subdev_get_fmt,
+ .set_fmt = fimc_isp_subdev_set_fmt,
+};
+
+static const struct v4l2_subdev_video_ops fimc_is_subdev_video_ops = {
+ .s_stream = fimc_isp_subdev_s_stream,
+};
+
+static const struct v4l2_subdev_core_ops fimc_is_core_ops = {
+ .s_power = fimc_isp_subdev_s_power,
+};
+
+static const struct v4l2_subdev_ops fimc_is_subdev_ops = {
+ .core = &fimc_is_core_ops,
+ .video = &fimc_is_subdev_video_ops,
+ .pad = &fimc_is_subdev_pad_ops,
+};
+
+static int __ctrl_set_white_balance(struct fimc_is *is, int value)
+{
+ switch (value) {
+ case V4L2_WHITE_BALANCE_AUTO:
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_AUTO, 0);
+ break;
+ case V4L2_WHITE_BALANCE_DAYLIGHT:
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION,
+ ISP_AWB_ILLUMINATION_DAYLIGHT);
+ break;
+ case V4L2_WHITE_BALANCE_CLOUDY:
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION,
+ ISP_AWB_ILLUMINATION_CLOUDY);
+ break;
+ case V4L2_WHITE_BALANCE_INCANDESCENT:
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION,
+ ISP_AWB_ILLUMINATION_TUNGSTEN);
+ break;
+ case V4L2_WHITE_BALANCE_FLUORESCENT:
+ __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION,
+ ISP_AWB_ILLUMINATION_FLUORESCENT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __ctrl_set_aewb_lock(struct fimc_is *is,
+ struct v4l2_ctrl *ctrl)
+{
+ bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE;
+ bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE;
+ struct isp_param *isp = &is->is_p_region->parameter.isp;
+ int cmd, ret;
+
+ cmd = ae_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START;
+ isp->aa.cmd = cmd;
+ isp->aa.target = ISP_AA_TARGET_AE;
+ fimc_is_set_param_bit(is, PARAM_ISP_AA);
+ is->af.ae_lock_state = ae_lock;
+ wmb();
+
+ ret = fimc_is_itf_s_param(is, false);
+ if (ret < 0)
+ return ret;
+
+ cmd = awb_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START;
+ isp->aa.cmd = cmd;
+ isp->aa.target = ISP_AA_TARGET_AE;
+ fimc_is_set_param_bit(is, PARAM_ISP_AA);
+ is->af.awb_lock_state = awb_lock;
+ wmb();
+
+ return fimc_is_itf_s_param(is, false);
+}
+
+/* Supported manual ISO values */
+static const s64 iso_qmenu[] = {
+ 50, 100, 200, 400, 800,
+};
+
+static int __ctrl_set_iso(struct fimc_is *is, int value)
+{
+ unsigned int idx, iso;
+
+ if (value == V4L2_ISO_SENSITIVITY_AUTO) {
+ __is_set_isp_iso(is, ISP_ISO_COMMAND_AUTO, 0);
+ return 0;
+ }
+ idx = is->isp.ctrls.iso->val;
+ if (idx >= ARRAY_SIZE(iso_qmenu))
+ return -EINVAL;
+
+ iso = iso_qmenu[idx];
+ __is_set_isp_iso(is, ISP_ISO_COMMAND_MANUAL, iso);
+ return 0;
+}
+
+static int __ctrl_set_metering(struct fimc_is *is, unsigned int value)
+{
+ unsigned int val;
+
+ switch (value) {
+ case V4L2_EXPOSURE_METERING_AVERAGE:
+ val = ISP_METERING_COMMAND_AVERAGE;
+ break;
+ case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED:
+ val = ISP_METERING_COMMAND_CENTER;
+ break;
+ case V4L2_EXPOSURE_METERING_SPOT:
+ val = ISP_METERING_COMMAND_SPOT;
+ break;
+ case V4L2_EXPOSURE_METERING_MATRIX:
+ val = ISP_METERING_COMMAND_MATRIX;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ __is_set_isp_metering(is, IS_METERING_CONFIG_CMD, val);
+ return 0;
+}
+
+static int __ctrl_set_afc(struct fimc_is *is, int value)
+{
+ switch (value) {
+ case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED:
+ __is_set_isp_afc(is, ISP_AFC_COMMAND_DISABLE, 0);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
+ __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 50);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
+ __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 60);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_AUTO:
+ __is_set_isp_afc(is, ISP_AFC_COMMAND_AUTO, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __ctrl_set_image_effect(struct fimc_is *is, int value)
+{
+ static const u8 effects[][2] = {
+ { V4L2_COLORFX_NONE, ISP_IMAGE_EFFECT_DISABLE },
+ { V4L2_COLORFX_BW, ISP_IMAGE_EFFECT_MONOCHROME },
+ { V4L2_COLORFX_SEPIA, ISP_IMAGE_EFFECT_SEPIA },
+ { V4L2_COLORFX_NEGATIVE, ISP_IMAGE_EFFECT_NEGATIVE_MONO },
+ { 16 /* TODO */, ISP_IMAGE_EFFECT_NEGATIVE_COLOR },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(effects); i++) {
+ if (effects[i][0] != value)
+ continue;
+
+ __is_set_isp_effect(is, effects[i][1]);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int fimc_is_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct fimc_isp *isp = ctrl_to_fimc_isp(ctrl);
+ struct fimc_is *is = fimc_isp_to_is(isp);
+ bool set_param = true;
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_CONTRAST:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_CONTRAST,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_SATURATION:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SATURATION,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_SHARPNESS:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SHARPNESS,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_EXPOSURE_ABSOLUTE:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_EXPOSURE,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_BRIGHTNESS:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_HUE:
+ __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_HUE,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_EXPOSURE_METERING:
+ ret = __ctrl_set_metering(is, ctrl->val);
+ break;
+
+ case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE:
+ ret = __ctrl_set_white_balance(is, ctrl->val);
+ break;
+
+ case V4L2_CID_3A_LOCK:
+ ret = __ctrl_set_aewb_lock(is, ctrl);
+ set_param = false;
+ break;
+
+ case V4L2_CID_ISO_SENSITIVITY_AUTO:
+ ret = __ctrl_set_iso(is, ctrl->val);
+ break;
+
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ ret = __ctrl_set_afc(is, ctrl->val);
+ break;
+
+ case V4L2_CID_COLORFX:
+ __ctrl_set_image_effect(is, ctrl->val);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret < 0) {
+ v4l2_err(&isp->subdev, "Failed to set control: %s (%d)\n",
+ ctrl->name, ctrl->val);
+ return ret;
+ }
+
+ if (set_param && test_bit(IS_ST_STREAM_ON, &is->state))
+ return fimc_is_itf_s_param(is, true);
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops fimc_isp_ctrl_ops = {
+ .s_ctrl = fimc_is_s_ctrl,
+};
+
+static void __isp_subdev_set_default_format(struct fimc_isp *isp)
+{
+ struct fimc_is *is = fimc_isp_to_is(isp);
+
+ isp->sink_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH +
+ FIMC_ISP_CAC_MARGIN_WIDTH;
+ isp->sink_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT +
+ FIMC_ISP_CAC_MARGIN_HEIGHT;
+ isp->sink_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+
+ isp->src_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH;
+ isp->src_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT;
+ isp->src_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10;
+ __is_set_frame_size(is, &isp->src_fmt);
+}
+
+int fimc_isp_subdev_create(struct fimc_isp *isp)
+{
+ const struct v4l2_ctrl_ops *ops = &fimc_isp_ctrl_ops;
+ struct v4l2_ctrl_handler *handler = &isp->ctrls.handler;
+ struct v4l2_subdev *sd = &isp->subdev;
+ struct fimc_isp_ctrls *ctrls = &isp->ctrls;
+ int ret;
+
+ mutex_init(&isp->subdev_lock);
+
+ v4l2_subdev_init(sd, &fimc_is_subdev_ops);
+
+ sd->owner = THIS_MODULE;
+ sd->grp_id = GRP_ID_FIMC_IS;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ snprintf(sd->name, sizeof(sd->name), "FIMC-IS-ISP");
+
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+ isp->subdev_pads[FIMC_ISP_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_FIFO].flags = MEDIA_PAD_FL_SOURCE;
+ isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_DMA].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&sd->entity, FIMC_ISP_SD_PADS_NUM,
+ isp->subdev_pads);
+ if (ret)
+ return ret;
+
+ v4l2_ctrl_handler_init(handler, 20);
+
+ ctrls->saturation = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SATURATION,
+ -2, 2, 1, 0);
+ ctrls->brightness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_BRIGHTNESS,
+ -4, 4, 1, 0);
+ ctrls->contrast = v4l2_ctrl_new_std(handler, ops, V4L2_CID_CONTRAST,
+ -2, 2, 1, 0);
+ ctrls->sharpness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SHARPNESS,
+ -2, 2, 1, 0);
+ ctrls->hue = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HUE,
+ -2, 2, 1, 0);
+
+ ctrls->auto_wb = v4l2_ctrl_new_std_menu(handler, ops,
+ V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
+ 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO);
+
+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops,
+ V4L2_CID_EXPOSURE_ABSOLUTE,
+ -4, 4, 1, 0);
+
+ ctrls->exp_metering = v4l2_ctrl_new_std_menu(handler, ops,
+ V4L2_CID_EXPOSURE_METERING, 3,
+ ~0xf, V4L2_EXPOSURE_METERING_AVERAGE);
+
+ v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_POWER_LINE_FREQUENCY,
+ V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0,
+ V4L2_CID_POWER_LINE_FREQUENCY_AUTO);
+ /* ISO sensitivity */
+ ctrls->auto_iso = v4l2_ctrl_new_std_menu(handler, ops,
+ V4L2_CID_ISO_SENSITIVITY_AUTO, 1, 0,
+ V4L2_ISO_SENSITIVITY_AUTO);
+
+ ctrls->iso = v4l2_ctrl_new_int_menu(handler, ops,
+ V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1,
+ ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu);
+
+ ctrls->aewb_lock = v4l2_ctrl_new_std(handler, ops,
+ V4L2_CID_3A_LOCK, 0, 0x3, 0, 0);
+
+ /* TODO: Add support for NEGATIVE_COLOR option */
+ ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_COLORFX,
+ V4L2_COLORFX_SET_CBCR + 1, ~0x1000f, V4L2_COLORFX_NONE);
+
+ if (handler->error) {
+ media_entity_cleanup(&sd->entity);
+ return handler->error;
+ }
+
+ v4l2_ctrl_auto_cluster(2, &ctrls->auto_iso,
+ V4L2_ISO_SENSITIVITY_MANUAL, false);
+
+ sd->ctrl_handler = handler;
+ sd->internal_ops = &fimc_is_subdev_internal_ops;
+ sd->entity.ops = &fimc_is_subdev_media_ops;
+ v4l2_set_subdevdata(sd, isp);
+
+ __isp_subdev_set_default_format(isp);
+
+ return 0;
+}
+
+void fimc_isp_subdev_destroy(struct fimc_isp *isp)
+{
+ struct v4l2_subdev *sd = &isp->subdev;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(&isp->ctrls.handler);
+ v4l2_set_subdevdata(sd, NULL);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp.h b/drivers/media/platform/samsung/exynos4-is/fimc-isp.h
new file mode 100644
index 0000000000..12017cd924
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.h
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver
+ *
+ * Copyright (C) 2013 Samsung Electronics Co., Ltd.
+ *
+ * Authors: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ * Younghwan Joo <yhwan.joo@samsung.com>
+ */
+#ifndef FIMC_ISP_H_
+#define FIMC_ISP_H_
+
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/media-entity.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mediabus.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+extern int fimc_isp_debug;
+
+#define isp_dbg(level, dev, fmt, arg...) \
+ v4l2_dbg(level, fimc_isp_debug, dev, fmt, ## arg)
+
+/* FIXME: revisit these constraints */
+#define FIMC_ISP_SINK_WIDTH_MIN (16 + 8)
+#define FIMC_ISP_SINK_HEIGHT_MIN (12 + 8)
+#define FIMC_ISP_SOURCE_WIDTH_MIN 8
+#define FIMC_ISP_SOURCE_HEIGHT_MIN 8
+#define FIMC_ISP_CAC_MARGIN_WIDTH 16
+#define FIMC_ISP_CAC_MARGIN_HEIGHT 12
+
+#define FIMC_ISP_SINK_WIDTH_MAX (4000 - 16)
+#define FIMC_ISP_SINK_HEIGHT_MAX (4000 + 12)
+#define FIMC_ISP_SOURCE_WIDTH_MAX 4000
+#define FIMC_ISP_SOURCE_HEIGHT_MAX 4000
+
+#define FIMC_ISP_NUM_FORMATS 3
+#define FIMC_ISP_REQ_BUFS_MIN 2
+#define FIMC_ISP_REQ_BUFS_MAX 32
+
+#define FIMC_ISP_SD_PAD_SINK 0
+#define FIMC_ISP_SD_PAD_SRC_FIFO 1
+#define FIMC_ISP_SD_PAD_SRC_DMA 2
+#define FIMC_ISP_SD_PADS_NUM 3
+#define FIMC_ISP_MAX_PLANES 1
+
+/**
+ * struct fimc_isp_frame - source/target frame properties
+ * @width: full image width
+ * @height: full image height
+ * @rect: crop/composition rectangle
+ */
+struct fimc_isp_frame {
+ u16 width;
+ u16 height;
+ struct v4l2_rect rect;
+};
+
+struct fimc_isp_ctrls {
+ struct v4l2_ctrl_handler handler;
+
+ /* Auto white balance */
+ struct v4l2_ctrl *auto_wb;
+ /* Auto ISO control cluster */
+ struct {
+ struct v4l2_ctrl *auto_iso;
+ struct v4l2_ctrl *iso;
+ };
+ /* Adjust - contrast */
+ struct v4l2_ctrl *contrast;
+ /* Adjust - saturation */
+ struct v4l2_ctrl *saturation;
+ /* Adjust - sharpness */
+ struct v4l2_ctrl *sharpness;
+ /* Adjust - brightness */
+ struct v4l2_ctrl *brightness;
+ /* Adjust - hue */
+ struct v4l2_ctrl *hue;
+
+ /* Auto/manual exposure */
+ struct v4l2_ctrl *auto_exp;
+ /* Manual exposure value */
+ struct v4l2_ctrl *exposure;
+ /* AE/AWB lock/unlock */
+ struct v4l2_ctrl *aewb_lock;
+ /* Exposure metering mode */
+ struct v4l2_ctrl *exp_metering;
+ /* AFC */
+ struct v4l2_ctrl *afc;
+ /* ISP image effect */
+ struct v4l2_ctrl *colorfx;
+};
+
+struct isp_video_buf {
+ struct vb2_v4l2_buffer vb;
+ dma_addr_t dma_addr[FIMC_ISP_MAX_PLANES];
+ unsigned int index;
+};
+
+#define to_isp_video_buf(_b) container_of(_b, struct isp_video_buf, vb)
+
+#define FIMC_ISP_MAX_BUFS 4
+
+/**
+ * struct fimc_is_video - fimc-is video device structure
+ * @ve: video_device structure and media pipeline
+ * @type: video device type (CAPTURE/OUTPUT)
+ * @pad: video device media (sink) pad
+ * @pending_buf_q: pending buffers queue head
+ * @active_buf_q: a queue head of buffers scheduled in hardware
+ * @vb_queue: vb2 buffer queue
+ * @reqbufs_count: the number of buffers requested in REQBUFS ioctl
+ * @buf_count: number of video buffers scheduled in hardware
+ * @buf_mask: bitmask of the queued video buffer indices
+ * @frame_count: counter of frames dequeued to user space
+ * @streaming: is streaming in progress?
+ * @buffers: buffer info
+ * @format: current fimc pixel format
+ * @pixfmt: current pixel format
+ */
+struct fimc_is_video {
+ struct exynos_video_entity ve;
+ enum v4l2_buf_type type;
+ struct media_pad pad;
+ struct list_head pending_buf_q;
+ struct list_head active_buf_q;
+ struct vb2_queue vb_queue;
+ unsigned int reqbufs_count;
+ unsigned int buf_count;
+ unsigned int buf_mask;
+ unsigned int frame_count;
+ int streaming;
+ struct isp_video_buf *buffers[FIMC_ISP_MAX_BUFS];
+ const struct fimc_fmt *format;
+ struct v4l2_pix_format_mplane pixfmt;
+};
+
+/* struct fimc_isp:state bit definitions */
+#define ST_ISP_VID_CAP_BUF_PREP 0
+#define ST_ISP_VID_CAP_STREAMING 1
+
+/**
+ * struct fimc_isp - FIMC-IS ISP data structure
+ * @pdev: pointer to FIMC-IS platform device
+ * @subdev: ISP v4l2_subdev
+ * @subdev_pads: the ISP subdev media pads
+ * @src_fmt: source mediabus format
+ * @sink_fmt: sink mediabus format
+ * @test_pattern: test pattern controls
+ * @ctrls: v4l2 controls structure
+ * @video_lock: mutex serializing video device operations
+ * @subdev_lock: mutex serializing subdev operations
+ * @cac_margin_x: horizontal CAC margin in pixels
+ * @cac_margin_y: vertical CAC margin in pixels
+ * @state: driver state flags
+ * @video_capture: the ISP block video capture device
+ */
+struct fimc_isp {
+ struct platform_device *pdev;
+ struct v4l2_subdev subdev;
+ struct media_pad subdev_pads[FIMC_ISP_SD_PADS_NUM];
+ struct v4l2_mbus_framefmt src_fmt;
+ struct v4l2_mbus_framefmt sink_fmt;
+ struct v4l2_ctrl *test_pattern;
+ struct fimc_isp_ctrls ctrls;
+
+ struct mutex video_lock;
+ struct mutex subdev_lock;
+
+ unsigned int cac_margin_x;
+ unsigned int cac_margin_y;
+
+ unsigned long state;
+
+ struct fimc_is_video video_capture;
+};
+
+#define ctrl_to_fimc_isp(_ctrl) \
+ container_of(ctrl->handler, struct fimc_isp, ctrls.handler)
+
+struct fimc_is;
+
+int fimc_isp_subdev_create(struct fimc_isp *isp);
+void fimc_isp_subdev_destroy(struct fimc_isp *isp);
+void fimc_isp_irq_handler(struct fimc_is *is);
+int fimc_is_create_controls(struct fimc_isp *isp);
+int fimc_is_delete_controls(struct fimc_isp *isp);
+const struct fimc_fmt *fimc_isp_find_format(const u32 *pixelformat,
+ const u32 *mbus_code, int index);
+#endif /* FIMC_ISP_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c
new file mode 100644
index 0000000000..57996b4104
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Register interface file for EXYNOS FIMC-LITE (camera interface) driver
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+*/
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#include "fimc-lite-reg.h"
+#include "fimc-lite.h"
+#include "fimc-core.h"
+
+#define FLITE_RESET_TIMEOUT 50 /* in ms */
+
+void flite_hw_reset(struct fimc_lite *dev)
+{
+ unsigned long end = jiffies + msecs_to_jiffies(FLITE_RESET_TIMEOUT);
+ u32 cfg;
+
+ cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ cfg |= FLITE_REG_CIGCTRL_SWRST_REQ;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+
+ while (time_is_after_jiffies(end)) {
+ cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ if (cfg & FLITE_REG_CIGCTRL_SWRST_RDY)
+ break;
+ usleep_range(1000, 5000);
+ }
+
+ cfg |= FLITE_REG_CIGCTRL_SWRST;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+}
+
+void flite_hw_clear_pending_irq(struct fimc_lite *dev)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS);
+ cfg &= ~FLITE_REG_CISTATUS_IRQ_CAM;
+ writel(cfg, dev->regs + FLITE_REG_CISTATUS);
+}
+
+u32 flite_hw_get_interrupt_source(struct fimc_lite *dev)
+{
+ u32 intsrc = readl(dev->regs + FLITE_REG_CISTATUS);
+ return intsrc & FLITE_REG_CISTATUS_IRQ_MASK;
+}
+
+void flite_hw_clear_last_capture_end(struct fimc_lite *dev)
+{
+
+ u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS2);
+ cfg &= ~FLITE_REG_CISTATUS2_LASTCAPEND;
+ writel(cfg, dev->regs + FLITE_REG_CISTATUS2);
+}
+
+void flite_hw_set_interrupt_mask(struct fimc_lite *dev)
+{
+ u32 cfg, intsrc;
+
+ /* Select interrupts to be enabled for each output mode */
+ if (atomic_read(&dev->out_path) == FIMC_IO_DMA) {
+ intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN |
+ FLITE_REG_CIGCTRL_IRQ_LASTEN |
+ FLITE_REG_CIGCTRL_IRQ_STARTEN |
+ FLITE_REG_CIGCTRL_IRQ_ENDEN;
+ } else {
+ /* An output to the FIMC-IS */
+ intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN |
+ FLITE_REG_CIGCTRL_IRQ_LASTEN;
+ }
+
+ cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ cfg |= FLITE_REG_CIGCTRL_IRQ_DISABLE_MASK;
+ cfg &= ~intsrc;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+}
+
+void flite_hw_capture_start(struct fimc_lite *dev)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT);
+ cfg |= FLITE_REG_CIIMGCPT_IMGCPTEN;
+ writel(cfg, dev->regs + FLITE_REG_CIIMGCPT);
+}
+
+void flite_hw_capture_stop(struct fimc_lite *dev)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT);
+ cfg &= ~FLITE_REG_CIIMGCPT_IMGCPTEN;
+ writel(cfg, dev->regs + FLITE_REG_CIIMGCPT);
+}
+
+/*
+ * Test pattern (color bars) enable/disable. External sensor
+ * pixel clock must be active for the test pattern to work.
+ */
+void flite_hw_set_test_pattern(struct fimc_lite *dev, bool on)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ if (on)
+ cfg |= FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR;
+ else
+ cfg &= ~FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+}
+
+static const u32 src_pixfmt_map[8][3] = {
+ { MEDIA_BUS_FMT_YUYV8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCBYCR,
+ FLITE_REG_CIGCTRL_YUV422_1P },
+ { MEDIA_BUS_FMT_YVYU8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCRYCB,
+ FLITE_REG_CIGCTRL_YUV422_1P },
+ { MEDIA_BUS_FMT_UYVY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CBYCRY,
+ FLITE_REG_CIGCTRL_YUV422_1P },
+ { MEDIA_BUS_FMT_VYUY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CRYCBY,
+ FLITE_REG_CIGCTRL_YUV422_1P },
+ { MEDIA_BUS_FMT_SGRBG8_1X8, 0, FLITE_REG_CIGCTRL_RAW8 },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, 0, FLITE_REG_CIGCTRL_RAW10 },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, 0, FLITE_REG_CIGCTRL_RAW12 },
+ { MEDIA_BUS_FMT_JPEG_1X8, 0, FLITE_REG_CIGCTRL_USER(1) },
+};
+
+/* Set camera input pixel format and resolution */
+void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f)
+{
+ u32 pixelcode = f->fmt->mbus_code;
+ int i = ARRAY_SIZE(src_pixfmt_map);
+ u32 cfg;
+
+ while (--i) {
+ if (src_pixfmt_map[i][0] == pixelcode)
+ break;
+ }
+
+ if (i == 0 && src_pixfmt_map[i][0] != pixelcode) {
+ v4l2_err(&dev->ve.vdev,
+ "Unsupported pixel code, falling back to %#08x\n",
+ src_pixfmt_map[i][0]);
+ }
+
+ cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ cfg &= ~FLITE_REG_CIGCTRL_FMT_MASK;
+ cfg |= src_pixfmt_map[i][2];
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+
+ cfg = readl(dev->regs + FLITE_REG_CISRCSIZE);
+ cfg &= ~(FLITE_REG_CISRCSIZE_ORDER422_MASK |
+ FLITE_REG_CISRCSIZE_SIZE_CAM_MASK);
+ cfg |= (f->f_width << 16) | f->f_height;
+ cfg |= src_pixfmt_map[i][1];
+ writel(cfg, dev->regs + FLITE_REG_CISRCSIZE);
+}
+
+/* Set the camera host input window offsets (cropping) */
+void flite_hw_set_window_offset(struct fimc_lite *dev, struct flite_frame *f)
+{
+ u32 hoff2, voff2;
+ u32 cfg;
+
+ cfg = readl(dev->regs + FLITE_REG_CIWDOFST);
+ cfg &= ~FLITE_REG_CIWDOFST_OFST_MASK;
+ cfg |= (f->rect.left << 16) | f->rect.top;
+ cfg |= FLITE_REG_CIWDOFST_WINOFSEN;
+ writel(cfg, dev->regs + FLITE_REG_CIWDOFST);
+
+ hoff2 = f->f_width - f->rect.width - f->rect.left;
+ voff2 = f->f_height - f->rect.height - f->rect.top;
+
+ cfg = (hoff2 << 16) | voff2;
+ writel(cfg, dev->regs + FLITE_REG_CIWDOFST2);
+}
+
+/* Select camera port (A, B) */
+static void flite_hw_set_camera_port(struct fimc_lite *dev, int id)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIGENERAL);
+ if (id == 0)
+ cfg &= ~FLITE_REG_CIGENERAL_CAM_B;
+ else
+ cfg |= FLITE_REG_CIGENERAL_CAM_B;
+ writel(cfg, dev->regs + FLITE_REG_CIGENERAL);
+}
+
+/* Select serial or parallel bus, camera port (A,B) and set signals polarity */
+void flite_hw_set_camera_bus(struct fimc_lite *dev,
+ struct fimc_source_info *si)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+ unsigned int flags = si->flags;
+
+ if (si->sensor_bus_type != FIMC_BUS_TYPE_MIPI_CSI2) {
+ cfg &= ~(FLITE_REG_CIGCTRL_SELCAM_MIPI |
+ FLITE_REG_CIGCTRL_INVPOLPCLK |
+ FLITE_REG_CIGCTRL_INVPOLVSYNC |
+ FLITE_REG_CIGCTRL_INVPOLHREF);
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= FLITE_REG_CIGCTRL_INVPOLPCLK;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ cfg |= FLITE_REG_CIGCTRL_INVPOLVSYNC;
+
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ cfg |= FLITE_REG_CIGCTRL_INVPOLHREF;
+ } else {
+ cfg |= FLITE_REG_CIGCTRL_SELCAM_MIPI;
+ }
+
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+
+ flite_hw_set_camera_port(dev, si->mux_id);
+}
+
+static void flite_hw_set_pack12(struct fimc_lite *dev, int on)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT);
+
+ cfg &= ~FLITE_REG_CIODMAFMT_PACK12;
+
+ if (on)
+ cfg |= FLITE_REG_CIODMAFMT_PACK12;
+
+ writel(cfg, dev->regs + FLITE_REG_CIODMAFMT);
+}
+
+static void flite_hw_set_out_order(struct fimc_lite *dev, struct flite_frame *f)
+{
+ static const u32 pixcode[4][2] = {
+ { MEDIA_BUS_FMT_YUYV8_2X8, FLITE_REG_CIODMAFMT_YCBYCR },
+ { MEDIA_BUS_FMT_YVYU8_2X8, FLITE_REG_CIODMAFMT_YCRYCB },
+ { MEDIA_BUS_FMT_UYVY8_2X8, FLITE_REG_CIODMAFMT_CBYCRY },
+ { MEDIA_BUS_FMT_VYUY8_2X8, FLITE_REG_CIODMAFMT_CRYCBY },
+ };
+ u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT);
+ int i = ARRAY_SIZE(pixcode);
+
+ while (--i)
+ if (pixcode[i][0] == f->fmt->mbus_code)
+ break;
+ cfg &= ~FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK;
+ writel(cfg | pixcode[i][1], dev->regs + FLITE_REG_CIODMAFMT);
+}
+
+void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f)
+{
+ u32 cfg;
+
+ /* Maximum output pixel size */
+ cfg = readl(dev->regs + FLITE_REG_CIOCAN);
+ cfg &= ~FLITE_REG_CIOCAN_MASK;
+ cfg |= (f->f_height << 16) | f->f_width;
+ writel(cfg, dev->regs + FLITE_REG_CIOCAN);
+
+ /* DMA offsets */
+ cfg = readl(dev->regs + FLITE_REG_CIOOFF);
+ cfg &= ~FLITE_REG_CIOOFF_MASK;
+ cfg |= (f->rect.top << 16) | f->rect.left;
+ writel(cfg, dev->regs + FLITE_REG_CIOOFF);
+}
+
+void flite_hw_set_dma_buffer(struct fimc_lite *dev, struct flite_buffer *buf)
+{
+ unsigned int index;
+ u32 cfg;
+
+ if (dev->dd->max_dma_bufs == 1)
+ index = 0;
+ else
+ index = buf->index;
+
+ if (index == 0)
+ writel(buf->addr, dev->regs + FLITE_REG_CIOSA);
+ else
+ writel(buf->addr, dev->regs + FLITE_REG_CIOSAN(index - 1));
+
+ cfg = readl(dev->regs + FLITE_REG_CIFCNTSEQ);
+ cfg |= BIT(index);
+ writel(cfg, dev->regs + FLITE_REG_CIFCNTSEQ);
+}
+
+void flite_hw_mask_dma_buffer(struct fimc_lite *dev, u32 index)
+{
+ u32 cfg;
+
+ if (dev->dd->max_dma_bufs == 1)
+ index = 0;
+
+ cfg = readl(dev->regs + FLITE_REG_CIFCNTSEQ);
+ cfg &= ~BIT(index);
+ writel(cfg, dev->regs + FLITE_REG_CIFCNTSEQ);
+}
+
+/* Enable/disable output DMA, set output pixel size and offsets (composition) */
+void flite_hw_set_output_dma(struct fimc_lite *dev, struct flite_frame *f,
+ bool enable)
+{
+ u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
+
+ if (!enable) {
+ cfg |= FLITE_REG_CIGCTRL_ODMA_DISABLE;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+ return;
+ }
+
+ cfg &= ~FLITE_REG_CIGCTRL_ODMA_DISABLE;
+ writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
+
+ flite_hw_set_out_order(dev, f);
+ flite_hw_set_dma_window(dev, f);
+ flite_hw_set_pack12(dev, 0);
+}
+
+void flite_hw_dump_regs(struct fimc_lite *dev, const char *label)
+{
+ struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ { 0x00, "CISRCSIZE" },
+ { 0x04, "CIGCTRL" },
+ { 0x08, "CIIMGCPT" },
+ { 0x0c, "CICPTSEQ" },
+ { 0x10, "CIWDOFST" },
+ { 0x14, "CIWDOFST2" },
+ { 0x18, "CIODMAFMT" },
+ { 0x20, "CIOCAN" },
+ { 0x24, "CIOOFF" },
+ { 0x30, "CIOSA" },
+ { 0x40, "CISTATUS" },
+ { 0x44, "CISTATUS2" },
+ { 0xf0, "CITHOLD" },
+ { 0xfc, "CIGENERAL" },
+ };
+ u32 i;
+
+ v4l2_info(&dev->subdev, "--- %s ---\n", label);
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 cfg = readl(dev->regs + registers[i].offset);
+ v4l2_info(&dev->subdev, "%9s: 0x%08x\n",
+ registers[i].name, cfg);
+ }
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h
new file mode 100644
index 0000000000..c5656e9027
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef FIMC_LITE_REG_H_
+#define FIMC_LITE_REG_H_
+
+#include <linux/bitops.h>
+
+#include "fimc-lite.h"
+
+/* Camera Source size */
+#define FLITE_REG_CISRCSIZE 0x00
+#define FLITE_REG_CISRCSIZE_ORDER422_IN_YCBYCR (0 << 14)
+#define FLITE_REG_CISRCSIZE_ORDER422_IN_YCRYCB (1 << 14)
+#define FLITE_REG_CISRCSIZE_ORDER422_IN_CBYCRY (2 << 14)
+#define FLITE_REG_CISRCSIZE_ORDER422_IN_CRYCBY (3 << 14)
+#define FLITE_REG_CISRCSIZE_ORDER422_MASK (0x3 << 14)
+#define FLITE_REG_CISRCSIZE_SIZE_CAM_MASK (0x3fff << 16 | 0x3fff)
+
+/* Global control */
+#define FLITE_REG_CIGCTRL 0x04
+#define FLITE_REG_CIGCTRL_YUV422_1P (0x1e << 24)
+#define FLITE_REG_CIGCTRL_RAW8 (0x2a << 24)
+#define FLITE_REG_CIGCTRL_RAW10 (0x2b << 24)
+#define FLITE_REG_CIGCTRL_RAW12 (0x2c << 24)
+#define FLITE_REG_CIGCTRL_RAW14 (0x2d << 24)
+/* User defined formats. x = 0...15 */
+#define FLITE_REG_CIGCTRL_USER(x) ((0x30 + x - 1) << 24)
+#define FLITE_REG_CIGCTRL_FMT_MASK (0x3f << 24)
+#define FLITE_REG_CIGCTRL_SHADOWMASK_DISABLE BIT(21)
+#define FLITE_REG_CIGCTRL_ODMA_DISABLE BIT(20)
+#define FLITE_REG_CIGCTRL_SWRST_REQ BIT(19)
+#define FLITE_REG_CIGCTRL_SWRST_RDY BIT(18)
+#define FLITE_REG_CIGCTRL_SWRST BIT(17)
+#define FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR BIT(15)
+#define FLITE_REG_CIGCTRL_INVPOLPCLK BIT(14)
+#define FLITE_REG_CIGCTRL_INVPOLVSYNC BIT(13)
+#define FLITE_REG_CIGCTRL_INVPOLHREF BIT(12)
+/* Interrupts mask bits (1 disables an interrupt) */
+#define FLITE_REG_CIGCTRL_IRQ_LASTEN BIT(8)
+#define FLITE_REG_CIGCTRL_IRQ_ENDEN BIT(7)
+#define FLITE_REG_CIGCTRL_IRQ_STARTEN BIT(6)
+#define FLITE_REG_CIGCTRL_IRQ_OVFEN BIT(5)
+#define FLITE_REG_CIGCTRL_IRQ_DISABLE_MASK (0xf << 5)
+#define FLITE_REG_CIGCTRL_SELCAM_MIPI BIT(3)
+
+/* Image Capture Enable */
+#define FLITE_REG_CIIMGCPT 0x08
+#define FLITE_REG_CIIMGCPT_IMGCPTEN BIT(31)
+#define FLITE_REG_CIIMGCPT_CPT_FREN BIT(25)
+#define FLITE_REG_CIIMGCPT_CPT_MOD_FRCNT (1 << 18)
+#define FLITE_REG_CIIMGCPT_CPT_MOD_FREN (0 << 18)
+
+/* Capture Sequence */
+#define FLITE_REG_CICPTSEQ 0x0c
+
+/* Camera Window Offset */
+#define FLITE_REG_CIWDOFST 0x10
+#define FLITE_REG_CIWDOFST_WINOFSEN BIT(31)
+#define FLITE_REG_CIWDOFST_CLROVIY BIT(31)
+#define FLITE_REG_CIWDOFST_CLROVFICB BIT(15)
+#define FLITE_REG_CIWDOFST_CLROVFICR BIT(14)
+#define FLITE_REG_CIWDOFST_OFST_MASK ((0x1fff << 16) | 0x1fff)
+
+/* Camera Window Offset2 */
+#define FLITE_REG_CIWDOFST2 0x14
+
+/* Camera Output DMA Format */
+#define FLITE_REG_CIODMAFMT 0x18
+#define FLITE_REG_CIODMAFMT_RAW_CON BIT(15)
+#define FLITE_REG_CIODMAFMT_PACK12 BIT(14)
+#define FLITE_REG_CIODMAFMT_YCBYCR (0 << 4)
+#define FLITE_REG_CIODMAFMT_YCRYCB (1 << 4)
+#define FLITE_REG_CIODMAFMT_CBYCRY (2 << 4)
+#define FLITE_REG_CIODMAFMT_CRYCBY (3 << 4)
+#define FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK (0x3 << 4)
+
+/* Camera Output Canvas */
+#define FLITE_REG_CIOCAN 0x20
+#define FLITE_REG_CIOCAN_MASK ((0x3fff << 16) | 0x3fff)
+
+/* Camera Output DMA Offset */
+#define FLITE_REG_CIOOFF 0x24
+#define FLITE_REG_CIOOFF_MASK ((0x3fff << 16) | 0x3fff)
+
+/* Camera Output DMA Start Address */
+#define FLITE_REG_CIOSA 0x30
+
+/* Camera Status */
+#define FLITE_REG_CISTATUS 0x40
+#define FLITE_REG_CISTATUS_MIPI_VVALID BIT(22)
+#define FLITE_REG_CISTATUS_MIPI_HVALID BIT(21)
+#define FLITE_REG_CISTATUS_MIPI_DVALID BIT(20)
+#define FLITE_REG_CISTATUS_ITU_VSYNC BIT(14)
+#define FLITE_REG_CISTATUS_ITU_HREFF BIT(13)
+#define FLITE_REG_CISTATUS_OVFIY BIT(10)
+#define FLITE_REG_CISTATUS_OVFICB BIT(9)
+#define FLITE_REG_CISTATUS_OVFICR BIT(8)
+#define FLITE_REG_CISTATUS_IRQ_SRC_OVERFLOW BIT(7)
+#define FLITE_REG_CISTATUS_IRQ_SRC_LASTCAPEND BIT(6)
+#define FLITE_REG_CISTATUS_IRQ_SRC_FRMSTART BIT(5)
+#define FLITE_REG_CISTATUS_IRQ_SRC_FRMEND BIT(4)
+#define FLITE_REG_CISTATUS_IRQ_CAM BIT(0)
+#define FLITE_REG_CISTATUS_IRQ_MASK (0xf << 4)
+
+/* Camera Status2 */
+#define FLITE_REG_CISTATUS2 0x44
+#define FLITE_REG_CISTATUS2_LASTCAPEND BIT(1)
+#define FLITE_REG_CISTATUS2_FRMEND BIT(0)
+
+/* Qos Threshold */
+#define FLITE_REG_CITHOLD 0xf0
+#define FLITE_REG_CITHOLD_W_QOS_EN BIT(30)
+
+/* Camera General Purpose */
+#define FLITE_REG_CIGENERAL 0xfc
+/* b0: 1 - camera B, 0 - camera A */
+#define FLITE_REG_CIGENERAL_CAM_B BIT(0)
+
+#define FLITE_REG_CIFCNTSEQ 0x100
+#define FLITE_REG_CIOSAN(x) (0x200 + (4 * (x)))
+
+/* ----------------------------------------------------------------------------
+ * Function declarations
+ */
+void flite_hw_reset(struct fimc_lite *dev);
+void flite_hw_clear_pending_irq(struct fimc_lite *dev);
+u32 flite_hw_get_interrupt_source(struct fimc_lite *dev);
+void flite_hw_clear_last_capture_end(struct fimc_lite *dev);
+void flite_hw_set_interrupt_mask(struct fimc_lite *dev);
+void flite_hw_capture_start(struct fimc_lite *dev);
+void flite_hw_capture_stop(struct fimc_lite *dev);
+void flite_hw_set_camera_bus(struct fimc_lite *dev,
+ struct fimc_source_info *s_info);
+void flite_hw_set_camera_polarity(struct fimc_lite *dev,
+ struct fimc_source_info *cam);
+void flite_hw_set_window_offset(struct fimc_lite *dev, struct flite_frame *f);
+void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f);
+
+void flite_hw_set_output_dma(struct fimc_lite *dev, struct flite_frame *f,
+ bool enable);
+void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f);
+void flite_hw_set_test_pattern(struct fimc_lite *dev, bool on);
+void flite_hw_dump_regs(struct fimc_lite *dev, const char *label);
+void flite_hw_set_dma_buffer(struct fimc_lite *dev, struct flite_buffer *buf);
+void flite_hw_mask_dma_buffer(struct fimc_lite *dev, u32 index);
+
+static inline void flite_hw_set_dma_buf_mask(struct fimc_lite *dev, u32 mask)
+{
+ writel(mask, dev->regs + FLITE_REG_CIFCNTSEQ);
+}
+
+#endif /* FIMC_LITE_REG_H */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c
new file mode 100644
index 0000000000..9396b10b5b
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c
@@ -0,0 +1,1668 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung EXYNOS FIMC-LITE (camera host interface) driver
+*
+ * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-rect.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#include "common.h"
+#include "fimc-core.h"
+#include "fimc-lite.h"
+#include "fimc-lite-reg.h"
+
+static int debug;
+module_param(debug, int, 0644);
+
+static const struct fimc_fmt fimc_lite_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCBYCR422,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .flags = FMT_FLAGS_YUV,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .depth = { 16 },
+ .color = FIMC_FMT_CBYCRY422,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .flags = FMT_FLAGS_YUV,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .depth = { 16 },
+ .color = FIMC_FMT_CRYCBY422,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .flags = FMT_FLAGS_YUV,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .colorspace = V4L2_COLORSPACE_JPEG,
+ .depth = { 16 },
+ .color = FIMC_FMT_YCRYCB422,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8,
+ .flags = FMT_FLAGS_YUV,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .depth = { 8 },
+ .color = FIMC_FMT_RAW8,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .flags = FMT_FLAGS_RAW_BAYER,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG10,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .depth = { 16 },
+ .color = FIMC_FMT_RAW10,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .flags = FMT_FLAGS_RAW_BAYER,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG12,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .depth = { 16 },
+ .color = FIMC_FMT_RAW12,
+ .memplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .flags = FMT_FLAGS_RAW_BAYER,
+ },
+};
+
+/**
+ * fimc_lite_find_format - lookup fimc color format by fourcc or media bus code
+ * @pixelformat: fourcc to match, ignored if null
+ * @mbus_code: media bus code to match, ignored if null
+ * @mask: the color format flags to match
+ * @index: index to the fimc_lite_formats array, ignored if negative
+ */
+static const struct fimc_fmt *fimc_lite_find_format(const u32 *pixelformat,
+ const u32 *mbus_code, unsigned int mask, int index)
+{
+ const struct fimc_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)ARRAY_SIZE(fimc_lite_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(fimc_lite_formats); ++i) {
+ fmt = &fimc_lite_formats[i];
+ if (mask && !(fmt->flags & mask))
+ continue;
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+static int fimc_lite_hw_init(struct fimc_lite *fimc, bool isp_output)
+{
+ struct fimc_source_info *si;
+ unsigned long flags;
+
+ if (fimc->sensor == NULL)
+ return -ENXIO;
+
+ if (fimc->inp_frame.fmt == NULL || fimc->out_frame.fmt == NULL)
+ return -EINVAL;
+
+ /* Get sensor configuration data from the sensor subdev */
+ si = v4l2_get_subdev_hostdata(fimc->sensor);
+ if (!si)
+ return -EINVAL;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+
+ flite_hw_set_camera_bus(fimc, si);
+ flite_hw_set_source_format(fimc, &fimc->inp_frame);
+ flite_hw_set_window_offset(fimc, &fimc->inp_frame);
+ flite_hw_set_dma_buf_mask(fimc, 0);
+ flite_hw_set_output_dma(fimc, &fimc->out_frame, !isp_output);
+ flite_hw_set_interrupt_mask(fimc);
+ flite_hw_set_test_pattern(fimc, fimc->test_pattern->val);
+
+ if (debug > 0)
+ flite_hw_dump_regs(fimc, __func__);
+
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return 0;
+}
+
+/*
+ * Reinitialize the driver so it is ready to start the streaming again.
+ * Set fimc->state to indicate stream off and the hardware shut down state.
+ * If not suspending (@suspend is false), return any buffers to videobuf2.
+ * Otherwise put any owned buffers onto the pending buffers queue, so they
+ * can be re-spun when the device is being resumed. Also perform FIMC
+ * software reset and disable streaming on the whole pipeline if required.
+ */
+static int fimc_lite_reinit(struct fimc_lite *fimc, bool suspend)
+{
+ struct flite_buffer *buf;
+ unsigned long flags;
+ bool streaming;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ streaming = fimc->state & (1 << ST_SENSOR_STREAM);
+
+ fimc->state &= ~(1 << ST_FLITE_RUN | 1 << ST_FLITE_OFF |
+ 1 << ST_FLITE_STREAM | 1 << ST_SENSOR_STREAM);
+ if (suspend)
+ fimc->state |= (1 << ST_FLITE_SUSPENDED);
+ else
+ fimc->state &= ~(1 << ST_FLITE_PENDING |
+ 1 << ST_FLITE_SUSPENDED);
+
+ /* Release unused buffers */
+ while (!suspend && !list_empty(&fimc->pending_buf_q)) {
+ buf = fimc_lite_pending_queue_pop(fimc);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ /* If suspending put unused buffers onto pending queue */
+ while (!list_empty(&fimc->active_buf_q)) {
+ buf = fimc_lite_active_queue_pop(fimc);
+ if (suspend)
+ fimc_lite_pending_queue_add(fimc, buf);
+ else
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ flite_hw_reset(fimc);
+
+ if (!streaming)
+ return 0;
+
+ return fimc_pipeline_call(&fimc->ve, set_stream, 0);
+}
+
+static int fimc_lite_stop_capture(struct fimc_lite *fimc, bool suspend)
+{
+ unsigned long flags;
+
+ if (!fimc_lite_active(fimc))
+ return 0;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ set_bit(ST_FLITE_OFF, &fimc->state);
+ flite_hw_capture_stop(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ wait_event_timeout(fimc->irq_queue,
+ !test_bit(ST_FLITE_OFF, &fimc->state),
+ (2*HZ/10)); /* 200 ms */
+
+ return fimc_lite_reinit(fimc, suspend);
+}
+
+/* Must be called with fimc.slock spinlock held. */
+static void fimc_lite_config_update(struct fimc_lite *fimc)
+{
+ flite_hw_set_window_offset(fimc, &fimc->inp_frame);
+ flite_hw_set_dma_window(fimc, &fimc->out_frame);
+ flite_hw_set_test_pattern(fimc, fimc->test_pattern->val);
+ clear_bit(ST_FLITE_CONFIG, &fimc->state);
+}
+
+static irqreturn_t flite_irq_handler(int irq, void *priv)
+{
+ struct fimc_lite *fimc = priv;
+ struct flite_buffer *vbuf;
+ unsigned long flags;
+ u32 intsrc;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+
+ intsrc = flite_hw_get_interrupt_source(fimc);
+ flite_hw_clear_pending_irq(fimc);
+
+ if (test_and_clear_bit(ST_FLITE_OFF, &fimc->state)) {
+ wake_up(&fimc->irq_queue);
+ goto done;
+ }
+
+ if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_OVERFLOW) {
+ clear_bit(ST_FLITE_RUN, &fimc->state);
+ fimc->events.data_overflow++;
+ }
+
+ if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_LASTCAPEND) {
+ flite_hw_clear_last_capture_end(fimc);
+ clear_bit(ST_FLITE_STREAM, &fimc->state);
+ wake_up(&fimc->irq_queue);
+ }
+
+ if (atomic_read(&fimc->out_path) != FIMC_IO_DMA)
+ goto done;
+
+ if ((intsrc & FLITE_REG_CISTATUS_IRQ_SRC_FRMSTART) &&
+ test_bit(ST_FLITE_RUN, &fimc->state) &&
+ !list_empty(&fimc->pending_buf_q)) {
+ vbuf = fimc_lite_pending_queue_pop(fimc);
+ flite_hw_set_dma_buffer(fimc, vbuf);
+ fimc_lite_active_queue_add(fimc, vbuf);
+ }
+
+ if ((intsrc & FLITE_REG_CISTATUS_IRQ_SRC_FRMEND) &&
+ test_bit(ST_FLITE_RUN, &fimc->state) &&
+ !list_empty(&fimc->active_buf_q)) {
+ vbuf = fimc_lite_active_queue_pop(fimc);
+ vbuf->vb.vb2_buf.timestamp = ktime_get_ns();
+ vbuf->vb.sequence = fimc->frame_count++;
+ flite_hw_mask_dma_buffer(fimc, vbuf->index);
+ vb2_buffer_done(&vbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ if (test_bit(ST_FLITE_CONFIG, &fimc->state))
+ fimc_lite_config_update(fimc);
+
+ if (list_empty(&fimc->pending_buf_q)) {
+ flite_hw_capture_stop(fimc);
+ clear_bit(ST_FLITE_STREAM, &fimc->state);
+ }
+done:
+ set_bit(ST_FLITE_RUN, &fimc->state);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return IRQ_HANDLED;
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct fimc_lite *fimc = q->drv_priv;
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+
+ fimc->buf_index = 0;
+ fimc->frame_count = 0;
+
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ ret = fimc_lite_hw_init(fimc, false);
+ if (ret) {
+ fimc_lite_reinit(fimc, false);
+ return ret;
+ }
+
+ set_bit(ST_FLITE_PENDING, &fimc->state);
+
+ if (!list_empty(&fimc->active_buf_q) &&
+ !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) {
+ flite_hw_capture_start(fimc);
+
+ if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state))
+ fimc_pipeline_call(&fimc->ve, set_stream, 1);
+ }
+ if (debug > 0)
+ flite_hw_dump_regs(fimc, __func__);
+
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct fimc_lite *fimc = q->drv_priv;
+
+ if (!fimc_lite_active(fimc))
+ return;
+
+ fimc_lite_stop_capture(fimc, false);
+}
+
+static int queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct fimc_lite *fimc = vq->drv_priv;
+ struct flite_frame *frame = &fimc->out_frame;
+ const struct fimc_fmt *fmt = frame->fmt;
+ unsigned long wh = frame->f_width * frame->f_height;
+ int i;
+
+ if (fmt == NULL)
+ return -EINVAL;
+
+ if (*num_planes) {
+ if (*num_planes != fmt->memplanes)
+ return -EINVAL;
+ for (i = 0; i < *num_planes; i++)
+ if (sizes[i] < (wh * fmt->depth[i]) / 8)
+ return -EINVAL;
+ return 0;
+ }
+
+ *num_planes = fmt->memplanes;
+
+ for (i = 0; i < fmt->memplanes; i++)
+ sizes[i] = (wh * fmt->depth[i]) / 8;
+
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct fimc_lite *fimc = vq->drv_priv;
+ int i;
+
+ if (fimc->out_frame.fmt == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < fimc->out_frame.fmt->memplanes; i++) {
+ unsigned long size = fimc->payload[i];
+
+ if (vb2_plane_size(vb, i) < size) {
+ v4l2_err(&fimc->ve.vdev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb, i), size);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb, i, size);
+ }
+
+ return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct flite_buffer *buf
+ = container_of(vbuf, struct flite_buffer, vb);
+ struct fimc_lite *fimc = vb2_get_drv_priv(vb->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ buf->addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ buf->index = fimc->buf_index++;
+ if (fimc->buf_index >= fimc->reqbufs_count)
+ fimc->buf_index = 0;
+
+ if (!test_bit(ST_FLITE_SUSPENDED, &fimc->state) &&
+ !test_bit(ST_FLITE_STREAM, &fimc->state) &&
+ list_empty(&fimc->active_buf_q)) {
+ flite_hw_set_dma_buffer(fimc, buf);
+ fimc_lite_active_queue_add(fimc, buf);
+ } else {
+ fimc_lite_pending_queue_add(fimc, buf);
+ }
+
+ if (vb2_is_streaming(&fimc->vb_queue) &&
+ !list_empty(&fimc->pending_buf_q) &&
+ !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) {
+ flite_hw_capture_start(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state))
+ fimc_pipeline_call(&fimc->ve, set_stream, 1);
+ return;
+ }
+ spin_unlock_irqrestore(&fimc->slock, flags);
+}
+
+static const struct vb2_ops fimc_lite_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+static void fimc_lite_clear_event_counters(struct fimc_lite *fimc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ memset(&fimc->events, 0, sizeof(fimc->events));
+ spin_unlock_irqrestore(&fimc->slock, flags);
+}
+
+static int fimc_lite_open(struct file *file)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct media_entity *me = &fimc->ve.vdev.entity;
+ int ret;
+
+ mutex_lock(&fimc->lock);
+ if (atomic_read(&fimc->out_path) != FIMC_IO_DMA) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ set_bit(ST_FLITE_IN_USE, &fimc->state);
+ ret = pm_runtime_resume_and_get(&fimc->pdev->dev);
+ if (ret < 0)
+ goto err_in_use;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto err_pm;
+
+ if (!v4l2_fh_is_singular_file(file) ||
+ atomic_read(&fimc->out_path) != FIMC_IO_DMA)
+ goto unlock;
+
+ mutex_lock(&me->graph_obj.mdev->graph_mutex);
+
+ ret = fimc_pipeline_call(&fimc->ve, open, me, true);
+
+ /* Mark video pipeline ending at this video node as in use. */
+ if (ret == 0)
+ me->use_count++;
+
+ mutex_unlock(&me->graph_obj.mdev->graph_mutex);
+
+ if (!ret) {
+ fimc_lite_clear_event_counters(fimc);
+ goto unlock;
+ }
+
+ v4l2_fh_release(file);
+err_pm:
+ pm_runtime_put_sync(&fimc->pdev->dev);
+err_in_use:
+ clear_bit(ST_FLITE_IN_USE, &fimc->state);
+unlock:
+ mutex_unlock(&fimc->lock);
+ return ret;
+}
+
+static int fimc_lite_release(struct file *file)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct media_entity *entity = &fimc->ve.vdev.entity;
+
+ mutex_lock(&fimc->lock);
+
+ if (v4l2_fh_is_singular_file(file) &&
+ atomic_read(&fimc->out_path) == FIMC_IO_DMA) {
+ if (fimc->streaming) {
+ video_device_pipeline_stop(&fimc->ve.vdev);
+ fimc->streaming = false;
+ }
+ fimc_lite_stop_capture(fimc, false);
+ fimc_pipeline_call(&fimc->ve, close);
+ clear_bit(ST_FLITE_IN_USE, &fimc->state);
+
+ mutex_lock(&entity->graph_obj.mdev->graph_mutex);
+ entity->use_count--;
+ mutex_unlock(&entity->graph_obj.mdev->graph_mutex);
+ }
+
+ _vb2_fop_release(file, NULL);
+ pm_runtime_put(&fimc->pdev->dev);
+ clear_bit(ST_FLITE_SUSPENDED, &fimc->state);
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static const struct v4l2_file_operations fimc_lite_fops = {
+ .owner = THIS_MODULE,
+ .open = fimc_lite_open,
+ .release = fimc_lite_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * Format and crop negotiation helpers
+ */
+
+static const struct fimc_fmt *fimc_lite_subdev_try_fmt(struct fimc_lite *fimc,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *format)
+{
+ struct flite_drvdata *dd = fimc->dd;
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ const struct fimc_fmt *fmt = NULL;
+
+ if (format->pad == FLITE_SD_PAD_SINK) {
+ v4l_bound_align_image(&mf->width, 8, dd->max_width,
+ ffs(dd->out_width_align) - 1,
+ &mf->height, 0, dd->max_height, 0, 0);
+
+ fmt = fimc_lite_find_format(NULL, &mf->code, 0, 0);
+ if (WARN_ON(!fmt))
+ return NULL;
+
+ mf->colorspace = fmt->colorspace;
+ mf->code = fmt->mbus_code;
+ } else {
+ struct flite_frame *sink = &fimc->inp_frame;
+ struct v4l2_mbus_framefmt *sink_fmt;
+ struct v4l2_rect *rect;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sink_fmt = v4l2_subdev_get_try_format(&fimc->subdev,
+ sd_state,
+ FLITE_SD_PAD_SINK);
+
+ mf->code = sink_fmt->code;
+ mf->colorspace = sink_fmt->colorspace;
+
+ rect = v4l2_subdev_get_try_crop(&fimc->subdev,
+ sd_state,
+ FLITE_SD_PAD_SINK);
+ } else {
+ mf->code = sink->fmt->mbus_code;
+ mf->colorspace = sink->fmt->colorspace;
+ rect = &sink->rect;
+ }
+
+ /* Allow changing format only on sink pad */
+ mf->width = rect->width;
+ mf->height = rect->height;
+ }
+
+ mf->field = V4L2_FIELD_NONE;
+
+ v4l2_dbg(1, debug, &fimc->subdev, "code: %#x (%d), %dx%d\n",
+ mf->code, mf->colorspace, mf->width, mf->height);
+
+ return fmt;
+}
+
+static void fimc_lite_try_crop(struct fimc_lite *fimc, struct v4l2_rect *r)
+{
+ struct flite_frame *frame = &fimc->inp_frame;
+
+ v4l_bound_align_image(&r->width, 0, frame->f_width, 0,
+ &r->height, 0, frame->f_height, 0, 0);
+
+ /* Adjust left/top if cropping rectangle got out of bounds */
+ r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width);
+ r->left = round_down(r->left, fimc->dd->win_hor_offs_align);
+ r->top = clamp_t(u32, r->top, 0, frame->f_height - r->height);
+
+ v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, sink fmt: %dx%d\n",
+ r->left, r->top, r->width, r->height,
+ frame->f_width, frame->f_height);
+}
+
+static void fimc_lite_try_compose(struct fimc_lite *fimc, struct v4l2_rect *r)
+{
+ struct flite_frame *frame = &fimc->out_frame;
+ struct v4l2_rect *crop_rect = &fimc->inp_frame.rect;
+
+ /* Scaling is not supported so we enforce compose rectangle size
+ same as size of the sink crop rectangle. */
+ r->width = crop_rect->width;
+ r->height = crop_rect->height;
+
+ /* Adjust left/top if the composing rectangle got out of bounds */
+ r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width);
+ r->left = round_down(r->left, fimc->dd->out_hor_offs_align);
+ r->top = clamp_t(u32, r->top, 0, fimc->out_frame.f_height - r->height);
+
+ v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, source fmt: %dx%d\n",
+ r->left, r->top, r->width, r->height,
+ frame->f_width, frame->f_height);
+}
+
+/*
+ * Video node ioctl operations
+ */
+static int fimc_lite_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, FIMC_LITE_DRV_NAME, sizeof(cap->driver));
+ strscpy(cap->card, FIMC_LITE_DRV_NAME, sizeof(cap->card));
+ return 0;
+}
+
+static int fimc_lite_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ const struct fimc_fmt *fmt;
+
+ if (f->index >= ARRAY_SIZE(fimc_lite_formats))
+ return -EINVAL;
+
+ fmt = &fimc_lite_formats[f->index];
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int fimc_lite_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
+ struct v4l2_plane_pix_format *plane_fmt = &pixm->plane_fmt[0];
+ struct flite_frame *frame = &fimc->out_frame;
+ const struct fimc_fmt *fmt = frame->fmt;
+
+ plane_fmt->bytesperline = (frame->f_width * fmt->depth[0]) / 8;
+ plane_fmt->sizeimage = plane_fmt->bytesperline * frame->f_height;
+
+ pixm->num_planes = fmt->memplanes;
+ pixm->pixelformat = fmt->fourcc;
+ pixm->width = frame->f_width;
+ pixm->height = frame->f_height;
+ pixm->field = V4L2_FIELD_NONE;
+ pixm->colorspace = fmt->colorspace;
+ return 0;
+}
+
+static int fimc_lite_try_fmt(struct fimc_lite *fimc,
+ struct v4l2_pix_format_mplane *pixm,
+ const struct fimc_fmt **ffmt)
+{
+ u32 bpl = pixm->plane_fmt[0].bytesperline;
+ struct flite_drvdata *dd = fimc->dd;
+ const struct fimc_fmt *inp_fmt = fimc->inp_frame.fmt;
+ const struct fimc_fmt *fmt;
+
+ if (WARN_ON(inp_fmt == NULL))
+ return -EINVAL;
+ /*
+ * We allow some flexibility only for YUV formats. In case of raw
+ * raw Bayer the FIMC-LITE's output format must match its camera
+ * interface input format.
+ */
+ if (inp_fmt->flags & FMT_FLAGS_YUV)
+ fmt = fimc_lite_find_format(&pixm->pixelformat, NULL,
+ inp_fmt->flags, 0);
+ else
+ fmt = inp_fmt;
+
+ if (WARN_ON(fmt == NULL))
+ return -EINVAL;
+ if (ffmt)
+ *ffmt = fmt;
+ v4l_bound_align_image(&pixm->width, 8, dd->max_width,
+ ffs(dd->out_width_align) - 1,
+ &pixm->height, 0, dd->max_height, 0, 0);
+
+ if ((bpl == 0 || ((bpl * 8) / fmt->depth[0]) < pixm->width))
+ pixm->plane_fmt[0].bytesperline = (pixm->width *
+ fmt->depth[0]) / 8;
+
+ if (pixm->plane_fmt[0].sizeimage == 0)
+ pixm->plane_fmt[0].sizeimage = (pixm->width * pixm->height *
+ fmt->depth[0]) / 8;
+ pixm->num_planes = fmt->memplanes;
+ pixm->pixelformat = fmt->fourcc;
+ pixm->colorspace = fmt->colorspace;
+ pixm->field = V4L2_FIELD_NONE;
+ return 0;
+}
+
+static int fimc_lite_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ return fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, NULL);
+}
+
+static int fimc_lite_s_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct flite_frame *frame = &fimc->out_frame;
+ const struct fimc_fmt *fmt = NULL;
+ int ret;
+
+ if (vb2_is_busy(&fimc->vb_queue))
+ return -EBUSY;
+
+ ret = fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, &fmt);
+ if (ret < 0)
+ return ret;
+
+ frame->fmt = fmt;
+ fimc->payload[0] = max((pixm->width * pixm->height * fmt->depth[0]) / 8,
+ pixm->plane_fmt[0].sizeimage);
+ frame->f_width = pixm->width;
+ frame->f_height = pixm->height;
+
+ return 0;
+}
+
+static int fimc_pipeline_validate(struct fimc_lite *fimc)
+{
+ struct v4l2_subdev *sd = &fimc->subdev;
+ struct v4l2_subdev_format sink_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_subdev_format src_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct media_pad *pad;
+ int ret;
+
+ while (1) {
+ /* Retrieve format at the sink pad */
+ pad = &sd->entity.pads[0];
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
+ break;
+ /* Don't call FIMC subdev operation to avoid nested locking */
+ if (sd == &fimc->subdev) {
+ struct flite_frame *ff = &fimc->out_frame;
+ sink_fmt.format.width = ff->f_width;
+ sink_fmt.format.height = ff->f_height;
+ sink_fmt.format.code = fimc->inp_frame.fmt->mbus_code;
+ } else {
+ sink_fmt.pad = pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL,
+ &sink_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+ }
+ /* Retrieve format at the source pad */
+ pad = media_pad_remote_pad_first(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+ src_fmt.pad = pad->index;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ if (src_fmt.format.width != sink_fmt.format.width ||
+ src_fmt.format.height != sink_fmt.format.height ||
+ src_fmt.format.code != sink_fmt.format.code)
+ return -EPIPE;
+ }
+ return 0;
+}
+
+static int fimc_lite_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ int ret;
+
+ if (fimc_lite_active(fimc))
+ return -EBUSY;
+
+ ret = video_device_pipeline_start(&fimc->ve.vdev, &fimc->ve.pipe->mp);
+ if (ret < 0)
+ return ret;
+
+ ret = fimc_pipeline_validate(fimc);
+ if (ret < 0)
+ goto err_p_stop;
+
+ fimc->sensor = fimc_find_remote_sensor(&fimc->subdev.entity);
+
+ ret = vb2_ioctl_streamon(file, priv, type);
+ if (!ret) {
+ fimc->streaming = true;
+ return ret;
+ }
+
+err_p_stop:
+ video_device_pipeline_stop(&fimc->ve.vdev);
+ return 0;
+}
+
+static int fimc_lite_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ int ret;
+
+ ret = vb2_ioctl_streamoff(file, priv, type);
+ if (ret < 0)
+ return ret;
+
+ video_device_pipeline_stop(&fimc->ve.vdev);
+ fimc->streaming = false;
+ return 0;
+}
+
+static int fimc_lite_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ int ret;
+
+ reqbufs->count = max_t(u32, FLITE_REQ_BUFS_MIN, reqbufs->count);
+ ret = vb2_ioctl_reqbufs(file, priv, reqbufs);
+ if (!ret)
+ fimc->reqbufs_count = reqbufs->count;
+
+ return ret;
+}
+
+static int fimc_lite_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *sel)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct flite_frame *f = &fimc->out_frame;
+
+ if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = f->f_width;
+ sel->r.height = f->f_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ sel->r = f->rect;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int fimc_lite_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *sel)
+{
+ struct fimc_lite *fimc = video_drvdata(file);
+ struct flite_frame *f = &fimc->out_frame;
+ struct v4l2_rect rect = sel->r;
+ unsigned long flags;
+
+ if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ sel->target != V4L2_SEL_TGT_COMPOSE)
+ return -EINVAL;
+
+ fimc_lite_try_compose(fimc, &rect);
+
+ if ((sel->flags & V4L2_SEL_FLAG_LE) &&
+ !v4l2_rect_enclosed(&rect, &sel->r))
+ return -ERANGE;
+
+ if ((sel->flags & V4L2_SEL_FLAG_GE) &&
+ !v4l2_rect_enclosed(&sel->r, &rect))
+ return -ERANGE;
+
+ sel->r = rect;
+ spin_lock_irqsave(&fimc->slock, flags);
+ f->rect = rect;
+ set_bit(ST_FLITE_CONFIG, &fimc->state);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops fimc_lite_ioctl_ops = {
+ .vidioc_querycap = fimc_lite_querycap,
+ .vidioc_enum_fmt_vid_cap = fimc_lite_enum_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = fimc_lite_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = fimc_lite_s_fmt_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = fimc_lite_g_fmt_mplane,
+ .vidioc_g_selection = fimc_lite_g_selection,
+ .vidioc_s_selection = fimc_lite_s_selection,
+ .vidioc_reqbufs = fimc_lite_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = fimc_lite_streamon,
+ .vidioc_streamoff = fimc_lite_streamoff,
+};
+
+/* Capture subdev media entity operations */
+static int fimc_lite_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ int ret = 0;
+
+ if (WARN_ON(fimc == NULL))
+ return 0;
+
+ v4l2_dbg(1, debug, sd, "%s: %s --> %s, flags: 0x%x. source_id: 0x%x\n",
+ __func__, remote->entity->name, local->entity->name,
+ flags, fimc->source_subdev_grp_id);
+
+ switch (local->index) {
+ case FLITE_SD_PAD_SINK:
+ if (flags & MEDIA_LNK_FL_ENABLED) {
+ if (fimc->source_subdev_grp_id == 0)
+ fimc->source_subdev_grp_id = sd->grp_id;
+ else
+ ret = -EBUSY;
+ } else {
+ fimc->source_subdev_grp_id = 0;
+ fimc->sensor = NULL;
+ }
+ break;
+
+ case FLITE_SD_PAD_SOURCE_DMA:
+ if (!(flags & MEDIA_LNK_FL_ENABLED))
+ atomic_set(&fimc->out_path, FIMC_IO_NONE);
+ else
+ atomic_set(&fimc->out_path, FIMC_IO_DMA);
+ break;
+
+ case FLITE_SD_PAD_SOURCE_ISP:
+ if (!(flags & MEDIA_LNK_FL_ENABLED))
+ atomic_set(&fimc->out_path, FIMC_IO_NONE);
+ else
+ atomic_set(&fimc->out_path, FIMC_IO_ISP);
+ break;
+
+ default:
+ v4l2_err(sd, "Invalid pad index\n");
+ ret = -EINVAL;
+ }
+ mb();
+
+ return ret;
+}
+
+static const struct media_entity_operations fimc_lite_subdev_media_ops = {
+ .link_setup = fimc_lite_link_setup,
+};
+
+static int fimc_lite_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ const struct fimc_fmt *fmt;
+
+ fmt = fimc_lite_find_format(NULL, NULL, 0, code->index);
+ if (!fmt)
+ return -EINVAL;
+ code->code = fmt->mbus_code;
+ return 0;
+}
+
+static struct v4l2_mbus_framefmt *__fimc_lite_subdev_get_try_fmt(
+ struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state, unsigned int pad)
+{
+ if (pad != FLITE_SD_PAD_SINK)
+ pad = FLITE_SD_PAD_SOURCE_DMA;
+
+ return v4l2_subdev_get_try_format(sd, sd_state, pad);
+}
+
+static int fimc_lite_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct flite_frame *f = &fimc->inp_frame;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = __fimc_lite_subdev_get_try_fmt(sd, sd_state, fmt->pad);
+ fmt->format = *mf;
+ return 0;
+ }
+
+ mutex_lock(&fimc->lock);
+ mf->colorspace = f->fmt->colorspace;
+ mf->code = f->fmt->mbus_code;
+
+ if (fmt->pad == FLITE_SD_PAD_SINK) {
+ /* full camera input frame size */
+ mf->width = f->f_width;
+ mf->height = f->f_height;
+ } else {
+ /* crop size */
+ mf->width = f->rect.width;
+ mf->height = f->rect.height;
+ }
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct flite_frame *sink = &fimc->inp_frame;
+ struct flite_frame *source = &fimc->out_frame;
+ const struct fimc_fmt *ffmt;
+
+ v4l2_dbg(1, debug, sd, "pad%d: code: 0x%x, %dx%d\n",
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ mutex_lock(&fimc->lock);
+
+ if ((atomic_read(&fimc->out_path) == FIMC_IO_ISP &&
+ media_entity_is_streaming(&sd->entity)) ||
+ (atomic_read(&fimc->out_path) == FIMC_IO_DMA &&
+ vb2_is_busy(&fimc->vb_queue))) {
+ mutex_unlock(&fimc->lock);
+ return -EBUSY;
+ }
+
+ ffmt = fimc_lite_subdev_try_fmt(fimc, sd_state, fmt);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ struct v4l2_mbus_framefmt *src_fmt;
+
+ mf = __fimc_lite_subdev_get_try_fmt(sd, sd_state, fmt->pad);
+ *mf = fmt->format;
+
+ if (fmt->pad == FLITE_SD_PAD_SINK) {
+ unsigned int pad = FLITE_SD_PAD_SOURCE_DMA;
+ src_fmt = __fimc_lite_subdev_get_try_fmt(sd, sd_state,
+ pad);
+ *src_fmt = *mf;
+ }
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+ }
+
+ if (fmt->pad == FLITE_SD_PAD_SINK) {
+ sink->f_width = mf->width;
+ sink->f_height = mf->height;
+ sink->fmt = ffmt;
+ /* Set sink crop rectangle */
+ sink->rect.width = mf->width;
+ sink->rect.height = mf->height;
+ sink->rect.left = 0;
+ sink->rect.top = 0;
+ /* Reset source format and crop rectangle */
+ source->rect = sink->rect;
+ source->f_width = mf->width;
+ source->f_height = mf->height;
+ }
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ struct flite_frame *f = &fimc->inp_frame;
+
+ if ((sel->target != V4L2_SEL_TGT_CROP &&
+ sel->target != V4L2_SEL_TGT_CROP_BOUNDS) ||
+ sel->pad != FLITE_SD_PAD_SINK)
+ return -EINVAL;
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ return 0;
+ }
+
+ mutex_lock(&fimc->lock);
+ if (sel->target == V4L2_SEL_TGT_CROP) {
+ sel->r = f->rect;
+ } else {
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = f->f_width;
+ sel->r.height = f->f_height;
+ }
+ mutex_unlock(&fimc->lock);
+
+ v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n",
+ __func__, f->rect.left, f->rect.top, f->rect.width,
+ f->rect.height, f->f_width, f->f_height);
+
+ return 0;
+}
+
+static int fimc_lite_subdev_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ struct flite_frame *f = &fimc->inp_frame;
+ int ret = 0;
+
+ if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != FLITE_SD_PAD_SINK)
+ return -EINVAL;
+
+ mutex_lock(&fimc->lock);
+ fimc_lite_try_crop(fimc, &sel->r);
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad) = sel->r;
+ } else {
+ unsigned long flags;
+ spin_lock_irqsave(&fimc->slock, flags);
+ f->rect = sel->r;
+ /* Same crop rectangle on the source pad */
+ fimc->out_frame.rect = sel->r;
+ set_bit(ST_FLITE_CONFIG, &fimc->state);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ }
+ mutex_unlock(&fimc->lock);
+
+ v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n",
+ __func__, f->rect.left, f->rect.top, f->rect.width,
+ f->rect.height, f->f_width, f->f_height);
+
+ return ret;
+}
+
+static int fimc_lite_subdev_s_stream(struct v4l2_subdev *sd, int on)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ unsigned long flags;
+ int ret;
+
+ /*
+ * Find sensor subdev linked to FIMC-LITE directly or through
+ * MIPI-CSIS. This is required for configuration where FIMC-LITE
+ * is used as a subdev only and feeds data internally to FIMC-IS.
+ * The pipeline links are protected through entity.pipe so there is no
+ * need to take the media graph mutex here.
+ */
+ fimc->sensor = fimc_find_remote_sensor(&sd->entity);
+
+ if (atomic_read(&fimc->out_path) != FIMC_IO_ISP)
+ return -ENOIOCTLCMD;
+
+ mutex_lock(&fimc->lock);
+ if (on) {
+ flite_hw_reset(fimc);
+ ret = fimc_lite_hw_init(fimc, true);
+ if (!ret) {
+ spin_lock_irqsave(&fimc->slock, flags);
+ flite_hw_capture_start(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ }
+ } else {
+ set_bit(ST_FLITE_OFF, &fimc->state);
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ flite_hw_capture_stop(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ ret = wait_event_timeout(fimc->irq_queue,
+ !test_bit(ST_FLITE_OFF, &fimc->state),
+ msecs_to_jiffies(200));
+ if (ret == 0)
+ v4l2_err(sd, "s_stream(0) timeout\n");
+ clear_bit(ST_FLITE_RUN, &fimc->state);
+ }
+
+ mutex_unlock(&fimc->lock);
+ return ret;
+}
+
+static int fimc_lite_log_status(struct v4l2_subdev *sd)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+
+ flite_hw_dump_regs(fimc, __func__);
+ return 0;
+}
+
+static int fimc_lite_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+ struct vb2_queue *q = &fimc->vb_queue;
+ struct video_device *vfd = &fimc->ve.vdev;
+ int ret;
+
+ memset(vfd, 0, sizeof(*vfd));
+ atomic_set(&fimc->out_path, FIMC_IO_DMA);
+
+ snprintf(vfd->name, sizeof(vfd->name), "fimc-lite.%d.capture",
+ fimc->index);
+
+ vfd->fops = &fimc_lite_fops;
+ vfd->ioctl_ops = &fimc_lite_ioctl_ops;
+ vfd->v4l2_dev = sd->v4l2_dev;
+ vfd->minor = -1;
+ vfd->release = video_device_release_empty;
+ vfd->queue = q;
+ vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
+ fimc->reqbufs_count = 0;
+
+ INIT_LIST_HEAD(&fimc->pending_buf_q);
+ INIT_LIST_HEAD(&fimc->active_buf_q);
+
+ memset(q, 0, sizeof(*q));
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->ops = &fimc_lite_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct flite_buffer);
+ q->drv_priv = fimc;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &fimc->lock;
+ q->dev = &fimc->pdev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret < 0)
+ return ret;
+
+ fimc->vd_pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vfd->entity, 1, &fimc->vd_pad);
+ if (ret < 0)
+ return ret;
+
+ video_set_drvdata(vfd, fimc);
+ fimc->ve.pipe = v4l2_get_subdev_hostdata(sd);
+
+ ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ media_entity_cleanup(&vfd->entity);
+ fimc->ve.pipe = NULL;
+ return ret;
+ }
+
+ v4l2_info(sd->v4l2_dev, "Registered %s as /dev/%s\n",
+ vfd->name, video_device_node_name(vfd));
+ return 0;
+}
+
+static void fimc_lite_subdev_unregistered(struct v4l2_subdev *sd)
+{
+ struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
+
+ if (fimc == NULL)
+ return;
+
+ mutex_lock(&fimc->lock);
+
+ if (video_is_registered(&fimc->ve.vdev)) {
+ video_unregister_device(&fimc->ve.vdev);
+ media_entity_cleanup(&fimc->ve.vdev.entity);
+ fimc->ve.pipe = NULL;
+ }
+
+ mutex_unlock(&fimc->lock);
+}
+
+static const struct v4l2_subdev_internal_ops fimc_lite_subdev_internal_ops = {
+ .registered = fimc_lite_subdev_registered,
+ .unregistered = fimc_lite_subdev_unregistered,
+};
+
+static const struct v4l2_subdev_pad_ops fimc_lite_subdev_pad_ops = {
+ .enum_mbus_code = fimc_lite_subdev_enum_mbus_code,
+ .get_selection = fimc_lite_subdev_get_selection,
+ .set_selection = fimc_lite_subdev_set_selection,
+ .get_fmt = fimc_lite_subdev_get_fmt,
+ .set_fmt = fimc_lite_subdev_set_fmt,
+};
+
+static const struct v4l2_subdev_video_ops fimc_lite_subdev_video_ops = {
+ .s_stream = fimc_lite_subdev_s_stream,
+};
+
+static const struct v4l2_subdev_core_ops fimc_lite_core_ops = {
+ .log_status = fimc_lite_log_status,
+};
+
+static const struct v4l2_subdev_ops fimc_lite_subdev_ops = {
+ .core = &fimc_lite_core_ops,
+ .video = &fimc_lite_subdev_video_ops,
+ .pad = &fimc_lite_subdev_pad_ops,
+};
+
+static int fimc_lite_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct fimc_lite *fimc = container_of(ctrl->handler, struct fimc_lite,
+ ctrl_handler);
+ set_bit(ST_FLITE_CONFIG, &fimc->state);
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops fimc_lite_ctrl_ops = {
+ .s_ctrl = fimc_lite_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config fimc_lite_ctrl = {
+ .ops = &fimc_lite_ctrl_ops,
+ .id = V4L2_CTRL_CLASS_USER | 0x1001,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Test Pattern 640x480",
+ .step = 1,
+};
+
+static void fimc_lite_set_default_config(struct fimc_lite *fimc)
+{
+ struct flite_frame *sink = &fimc->inp_frame;
+ struct flite_frame *source = &fimc->out_frame;
+
+ sink->fmt = &fimc_lite_formats[0];
+ sink->f_width = FLITE_DEFAULT_WIDTH;
+ sink->f_height = FLITE_DEFAULT_HEIGHT;
+
+ sink->rect.width = FLITE_DEFAULT_WIDTH;
+ sink->rect.height = FLITE_DEFAULT_HEIGHT;
+ sink->rect.left = 0;
+ sink->rect.top = 0;
+
+ *source = *sink;
+}
+
+static int fimc_lite_create_capture_subdev(struct fimc_lite *fimc)
+{
+ struct v4l2_ctrl_handler *handler = &fimc->ctrl_handler;
+ struct v4l2_subdev *sd = &fimc->subdev;
+ int ret;
+
+ v4l2_subdev_init(sd, &fimc_lite_subdev_ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ snprintf(sd->name, sizeof(sd->name), "FIMC-LITE.%d", fimc->index);
+
+ fimc->subdev_pads[FLITE_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ fimc->subdev_pads[FLITE_SD_PAD_SOURCE_DMA].flags = MEDIA_PAD_FL_SOURCE;
+ fimc->subdev_pads[FLITE_SD_PAD_SOURCE_ISP].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&sd->entity, FLITE_SD_PADS_NUM,
+ fimc->subdev_pads);
+ if (ret)
+ return ret;
+
+ v4l2_ctrl_handler_init(handler, 1);
+ fimc->test_pattern = v4l2_ctrl_new_custom(handler, &fimc_lite_ctrl,
+ NULL);
+ if (handler->error) {
+ media_entity_cleanup(&sd->entity);
+ return handler->error;
+ }
+
+ sd->ctrl_handler = handler;
+ sd->internal_ops = &fimc_lite_subdev_internal_ops;
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ sd->entity.ops = &fimc_lite_subdev_media_ops;
+ sd->owner = THIS_MODULE;
+ v4l2_set_subdevdata(sd, fimc);
+
+ return 0;
+}
+
+static void fimc_lite_unregister_capture_subdev(struct fimc_lite *fimc)
+{
+ struct v4l2_subdev *sd = &fimc->subdev;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(&fimc->ctrl_handler);
+ v4l2_set_subdevdata(sd, NULL);
+}
+
+static void fimc_lite_clk_put(struct fimc_lite *fimc)
+{
+ if (IS_ERR(fimc->clock))
+ return;
+
+ clk_put(fimc->clock);
+ fimc->clock = ERR_PTR(-EINVAL);
+}
+
+static int fimc_lite_clk_get(struct fimc_lite *fimc)
+{
+ fimc->clock = clk_get(&fimc->pdev->dev, FLITE_CLK_NAME);
+ return PTR_ERR_OR_ZERO(fimc->clock);
+}
+
+static const struct of_device_id flite_of_match[];
+
+static int fimc_lite_probe(struct platform_device *pdev)
+{
+ struct flite_drvdata *drv_data = NULL;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id;
+ struct fimc_lite *fimc;
+ int ret;
+ int irq;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL);
+ if (!fimc)
+ return -ENOMEM;
+
+ of_id = of_match_node(flite_of_match, dev->of_node);
+ if (of_id)
+ drv_data = (struct flite_drvdata *)of_id->data;
+ fimc->index = of_alias_get_id(dev->of_node, "fimc-lite");
+
+ if (!drv_data || fimc->index >= drv_data->num_instances ||
+ fimc->index < 0) {
+ dev_err(dev, "Wrong %pOF node alias\n", dev->of_node);
+ return -EINVAL;
+ }
+
+ fimc->dd = drv_data;
+ fimc->pdev = pdev;
+
+ init_waitqueue_head(&fimc->irq_queue);
+ spin_lock_init(&fimc->slock);
+ mutex_init(&fimc->lock);
+
+ fimc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(fimc->regs))
+ return PTR_ERR(fimc->regs);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = fimc_lite_clk_get(fimc);
+ if (ret)
+ return ret;
+
+ ret = devm_request_irq(dev, irq, flite_irq_handler,
+ 0, dev_name(dev), fimc);
+ if (ret) {
+ dev_err(dev, "Failed to install irq (%d)\n", ret);
+ goto err_clk_put;
+ }
+
+ /* The video node will be created within the subdev's registered() op */
+ ret = fimc_lite_create_capture_subdev(fimc);
+ if (ret)
+ goto err_clk_put;
+
+ platform_set_drvdata(pdev, fimc);
+ pm_runtime_enable(dev);
+
+ if (!pm_runtime_enabled(dev)) {
+ ret = clk_prepare_enable(fimc->clock);
+ if (ret < 0)
+ goto err_sd;
+ }
+
+ vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ fimc_lite_set_default_config(fimc);
+
+ dev_dbg(dev, "FIMC-LITE.%d registered successfully\n",
+ fimc->index);
+ return 0;
+
+err_sd:
+ fimc_lite_unregister_capture_subdev(fimc);
+err_clk_put:
+ fimc_lite_clk_put(fimc);
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int fimc_lite_runtime_resume(struct device *dev)
+{
+ struct fimc_lite *fimc = dev_get_drvdata(dev);
+
+ clk_prepare_enable(fimc->clock);
+ return 0;
+}
+
+static int fimc_lite_runtime_suspend(struct device *dev)
+{
+ struct fimc_lite *fimc = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(fimc->clock);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int fimc_lite_resume(struct device *dev)
+{
+ struct fimc_lite *fimc = dev_get_drvdata(dev);
+ struct flite_buffer *buf;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ if (!test_and_clear_bit(ST_LPM, &fimc->state) ||
+ !test_bit(ST_FLITE_IN_USE, &fimc->state)) {
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return 0;
+ }
+ flite_hw_reset(fimc);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+
+ if (!test_and_clear_bit(ST_FLITE_SUSPENDED, &fimc->state))
+ return 0;
+
+ INIT_LIST_HEAD(&fimc->active_buf_q);
+ fimc_pipeline_call(&fimc->ve, open,
+ &fimc->ve.vdev.entity, false);
+ fimc_lite_hw_init(fimc, atomic_read(&fimc->out_path) == FIMC_IO_ISP);
+ clear_bit(ST_FLITE_SUSPENDED, &fimc->state);
+
+ for (i = 0; i < fimc->reqbufs_count; i++) {
+ if (list_empty(&fimc->pending_buf_q))
+ break;
+ buf = fimc_lite_pending_queue_pop(fimc);
+ buffer_queue(&buf->vb.vb2_buf);
+ }
+ return 0;
+}
+
+static int fimc_lite_suspend(struct device *dev)
+{
+ struct fimc_lite *fimc = dev_get_drvdata(dev);
+ bool suspend = test_bit(ST_FLITE_IN_USE, &fimc->state);
+ int ret;
+
+ if (test_and_set_bit(ST_LPM, &fimc->state))
+ return 0;
+
+ ret = fimc_lite_stop_capture(fimc, suspend);
+ if (ret < 0 || !fimc_lite_active(fimc))
+ return ret;
+
+ return fimc_pipeline_call(&fimc->ve, close);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static void fimc_lite_remove(struct platform_device *pdev)
+{
+ struct fimc_lite *fimc = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ if (!pm_runtime_enabled(dev))
+ clk_disable_unprepare(fimc->clock);
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ fimc_lite_unregister_capture_subdev(fimc);
+ vb2_dma_contig_clear_max_seg_size(dev);
+ fimc_lite_clk_put(fimc);
+
+ dev_info(dev, "Driver unloaded\n");
+}
+
+static const struct dev_pm_ops fimc_lite_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(fimc_lite_suspend, fimc_lite_resume)
+ SET_RUNTIME_PM_OPS(fimc_lite_runtime_suspend, fimc_lite_runtime_resume,
+ NULL)
+};
+
+/* EXYNOS4212, EXYNOS4412 */
+static struct flite_drvdata fimc_lite_drvdata_exynos4 = {
+ .max_width = 8192,
+ .max_height = 8192,
+ .out_width_align = 8,
+ .win_hor_offs_align = 2,
+ .out_hor_offs_align = 8,
+ .max_dma_bufs = 1,
+ .num_instances = 2,
+};
+
+/* EXYNOS5250 */
+static struct flite_drvdata fimc_lite_drvdata_exynos5 = {
+ .max_width = 8192,
+ .max_height = 8192,
+ .out_width_align = 8,
+ .win_hor_offs_align = 2,
+ .out_hor_offs_align = 8,
+ .max_dma_bufs = 32,
+ .num_instances = 3,
+};
+
+static const struct of_device_id flite_of_match[] = {
+ {
+ .compatible = "samsung,exynos4212-fimc-lite",
+ .data = &fimc_lite_drvdata_exynos4,
+ },
+ {
+ .compatible = "samsung,exynos5250-fimc-lite",
+ .data = &fimc_lite_drvdata_exynos5,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, flite_of_match);
+
+static struct platform_driver fimc_lite_driver = {
+ .probe = fimc_lite_probe,
+ .remove_new = fimc_lite_remove,
+ .driver = {
+ .of_match_table = flite_of_match,
+ .name = FIMC_LITE_DRV_NAME,
+ .pm = &fimc_lite_pm_ops,
+ }
+};
+module_platform_driver(fimc_lite_driver);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" FIMC_LITE_DRV_NAME);
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite.h b/drivers/media/platform/samsung/exynos4-is/fimc-lite.h
new file mode 100644
index 0000000000..ddf29e0b5b
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.h
@@ -0,0 +1,224 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef FIMC_LITE_H_
+#define FIMC_LITE_H_
+
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include <linux/irqreturn.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/media-entity.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mediabus.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#define FIMC_LITE_DRV_NAME "exynos-fimc-lite"
+#define FLITE_CLK_NAME "flite"
+#define FIMC_LITE_MAX_DEVS 3
+#define FLITE_REQ_BUFS_MIN 2
+#define FLITE_DEFAULT_WIDTH 640
+#define FLITE_DEFAULT_HEIGHT 480
+
+/* Bit index definitions for struct fimc_lite::state */
+enum {
+ ST_FLITE_LPM,
+ ST_FLITE_PENDING,
+ ST_FLITE_RUN,
+ ST_FLITE_STREAM,
+ ST_FLITE_SUSPENDED,
+ ST_FLITE_OFF,
+ ST_FLITE_IN_USE,
+ ST_FLITE_CONFIG,
+ ST_SENSOR_STREAM,
+};
+
+#define FLITE_SD_PAD_SINK 0
+#define FLITE_SD_PAD_SOURCE_DMA 1
+#define FLITE_SD_PAD_SOURCE_ISP 2
+#define FLITE_SD_PADS_NUM 3
+
+/**
+ * struct flite_drvdata - FIMC-LITE IP variant data structure
+ * @max_width: maximum camera interface input width in pixels
+ * @max_height: maximum camera interface input height in pixels
+ * @out_width_align: minimum output width alignment in pixels
+ * @win_hor_offs_align: minimum camera interface crop window horizontal
+ * offset alignment in pixels
+ * @out_hor_offs_align: minimum output DMA compose rectangle horizontal
+ * offset alignment in pixels
+ * @max_dma_bufs: number of output DMA buffer start address registers
+ * @num_instances: total number of FIMC-LITE IP instances available
+ */
+struct flite_drvdata {
+ unsigned short max_width;
+ unsigned short max_height;
+ unsigned short out_width_align;
+ unsigned short win_hor_offs_align;
+ unsigned short out_hor_offs_align;
+ unsigned short max_dma_bufs;
+ unsigned short num_instances;
+};
+
+struct fimc_lite_events {
+ unsigned int data_overflow;
+};
+
+#define FLITE_MAX_PLANES 1
+
+/**
+ * struct flite_frame - source/target frame properties
+ * @f_width: full pixel width
+ * @f_height: full pixel height
+ * @rect: crop/composition rectangle
+ * @fmt: pointer to pixel format description data structure
+ */
+struct flite_frame {
+ u16 f_width;
+ u16 f_height;
+ struct v4l2_rect rect;
+ const struct fimc_fmt *fmt;
+};
+
+/**
+ * struct flite_buffer - video buffer structure
+ * @vb: vb2 buffer
+ * @list: list head for the buffers queue
+ * @addr: DMA buffer start address
+ * @index: DMA start address register's index
+ */
+struct flite_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ dma_addr_t addr;
+ unsigned short index;
+};
+
+/**
+ * struct fimc_lite - fimc lite structure
+ * @pdev: pointer to FIMC-LITE platform device
+ * @dd: SoC specific driver data structure
+ * @ve: exynos video device entity structure
+ * @v4l2_dev: pointer to top the level v4l2_device
+ * @fh: v4l2 file handle
+ * @subdev: FIMC-LITE subdev
+ * @vd_pad: media (sink) pad for the capture video node
+ * @subdev_pads: the subdev media pads
+ * @sensor: sensor subdev attached to FIMC-LITE directly or through MIPI-CSIS
+ * @ctrl_handler: v4l2 control handler
+ * @test_pattern: test pattern controls
+ * @index: FIMC-LITE platform device index
+ * @pipeline: video capture pipeline data structure
+ * @pipeline_ops: media pipeline ops for the video node driver
+ * @slock: spinlock protecting this data structure and the hw registers
+ * @lock: mutex serializing video device and the subdev operations
+ * @clock: FIMC-LITE gate clock
+ * @regs: memory mapped io registers
+ * @irq_queue: interrupt handler waitqueue
+ * @payload: image size in bytes (w x h x bpp)
+ * @inp_frame: camera input frame structure
+ * @out_frame: DMA output frame structure
+ * @out_path: output data path (DMA or FIFO)
+ * @source_subdev_grp_id: source subdev group id
+ * @state: driver state flags
+ * @pending_buf_q: pending buffers queue head
+ * @active_buf_q: the queue head of buffers scheduled in hardware
+ * @vb_queue: vb2 buffers queue
+ * @buf_index: helps to keep track of the DMA start address register index
+ * @active_buf_count: number of video buffers scheduled in hardware
+ * @frame_count: the captured frames counter
+ * @reqbufs_count: the number of buffers requested with REQBUFS ioctl
+ * @events: event info
+ * @streaming: is streaming in progress?
+ */
+struct fimc_lite {
+ struct platform_device *pdev;
+ struct flite_drvdata *dd;
+ struct exynos_video_entity ve;
+ struct v4l2_device *v4l2_dev;
+ struct v4l2_fh fh;
+ struct v4l2_subdev subdev;
+ struct media_pad vd_pad;
+ struct media_pad subdev_pads[FLITE_SD_PADS_NUM];
+ struct v4l2_subdev *sensor;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_ctrl *test_pattern;
+ int index;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ struct clk *clock;
+ void __iomem *regs;
+ wait_queue_head_t irq_queue;
+
+ unsigned long payload[FLITE_MAX_PLANES];
+ struct flite_frame inp_frame;
+ struct flite_frame out_frame;
+ atomic_t out_path;
+ unsigned int source_subdev_grp_id;
+
+ unsigned long state;
+ struct list_head pending_buf_q;
+ struct list_head active_buf_q;
+ struct vb2_queue vb_queue;
+ unsigned short buf_index;
+ unsigned int frame_count;
+ unsigned int reqbufs_count;
+
+ struct fimc_lite_events events;
+ bool streaming;
+};
+
+static inline bool fimc_lite_active(struct fimc_lite *fimc)
+{
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&fimc->slock, flags);
+ ret = fimc->state & (1 << ST_FLITE_RUN) ||
+ fimc->state & (1 << ST_FLITE_PENDING);
+ spin_unlock_irqrestore(&fimc->slock, flags);
+ return ret;
+}
+
+static inline void fimc_lite_active_queue_add(struct fimc_lite *dev,
+ struct flite_buffer *buf)
+{
+ list_add_tail(&buf->list, &dev->active_buf_q);
+}
+
+static inline struct flite_buffer *fimc_lite_active_queue_pop(
+ struct fimc_lite *dev)
+{
+ struct flite_buffer *buf = list_entry(dev->active_buf_q.next,
+ struct flite_buffer, list);
+ list_del(&buf->list);
+ return buf;
+}
+
+static inline void fimc_lite_pending_queue_add(struct fimc_lite *dev,
+ struct flite_buffer *buf)
+{
+ list_add_tail(&buf->list, &dev->pending_buf_q);
+}
+
+static inline struct flite_buffer *fimc_lite_pending_queue_pop(
+ struct fimc_lite *dev)
+{
+ struct flite_buffer *buf = list_entry(dev->pending_buf_q.next,
+ struct flite_buffer, list);
+ list_del(&buf->list);
+ return buf;
+}
+
+#endif /* FIMC_LITE_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c b/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c
new file mode 100644
index 0000000000..df8e2aa454
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c
@@ -0,0 +1,773 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S5P/EXYNOS4 SoC series FIMC (video postprocessor) driver
+ *
+ * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "common.h"
+#include "fimc-core.h"
+#include "fimc-reg.h"
+#include "media-dev.h"
+
+static unsigned int get_m2m_fmt_flags(unsigned int stream_type)
+{
+ if (stream_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return FMT_FLAGS_M2M_IN;
+ else
+ return FMT_FLAGS_M2M_OUT;
+}
+
+void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state)
+{
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+
+ if (!ctx || !ctx->fh.m2m_ctx)
+ return;
+
+ src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+ if (src_vb)
+ v4l2_m2m_buf_done(src_vb, vb_state);
+ if (dst_vb)
+ v4l2_m2m_buf_done(dst_vb, vb_state);
+ if (src_vb && dst_vb)
+ v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev,
+ ctx->fh.m2m_ctx);
+}
+
+/* Complete the transaction which has been scheduled for execution. */
+static void fimc_m2m_shutdown(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+
+ if (!fimc_m2m_pending(fimc))
+ return;
+
+ fimc_ctx_state_set(FIMC_CTX_SHUT, ctx);
+
+ wait_event_timeout(fimc->irq_queue,
+ !fimc_ctx_state_is_set(FIMC_CTX_SHUT, ctx),
+ FIMC_SHUTDOWN_TIMEOUT);
+}
+
+static int start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct fimc_ctx *ctx = q->drv_priv;
+
+ return pm_runtime_resume_and_get(&ctx->fimc_dev->pdev->dev);
+}
+
+static void stop_streaming(struct vb2_queue *q)
+{
+ struct fimc_ctx *ctx = q->drv_priv;
+
+ fimc_m2m_shutdown(ctx);
+ fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
+ pm_runtime_put(&ctx->fimc_dev->pdev->dev);
+}
+
+static void fimc_device_run(void *priv)
+{
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+ struct fimc_ctx *ctx = priv;
+ struct fimc_frame *sf, *df;
+ struct fimc_dev *fimc;
+ unsigned long flags;
+ int ret;
+
+ if (WARN(!ctx, "Null context\n"))
+ return;
+
+ fimc = ctx->fimc_dev;
+ spin_lock_irqsave(&fimc->slock, flags);
+
+ set_bit(ST_M2M_PEND, &fimc->state);
+ sf = &ctx->s_frame;
+ df = &ctx->d_frame;
+
+ if (ctx->state & FIMC_PARAMS) {
+ /* Prepare the DMA offsets for scaler */
+ fimc_prepare_dma_offset(ctx, sf);
+ fimc_prepare_dma_offset(ctx, df);
+ }
+
+ src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ ret = fimc_prepare_addr(ctx, &src_vb->vb2_buf, sf, &sf->addr);
+ if (ret)
+ goto dma_unlock;
+
+ dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ ret = fimc_prepare_addr(ctx, &dst_vb->vb2_buf, df, &df->addr);
+ if (ret)
+ goto dma_unlock;
+
+ dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp;
+ dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ dst_vb->flags |=
+ src_vb->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+
+ /* Reconfigure hardware if the context has changed. */
+ if (fimc->m2m.ctx != ctx) {
+ ctx->state |= FIMC_PARAMS;
+ fimc->m2m.ctx = ctx;
+ }
+
+ if (ctx->state & FIMC_PARAMS) {
+ fimc_set_yuv_order(ctx);
+ fimc_hw_set_input_path(ctx);
+ fimc_hw_set_in_dma(ctx);
+ ret = fimc_set_scaler_info(ctx);
+ if (ret)
+ goto dma_unlock;
+ fimc_hw_set_prescaler(ctx);
+ fimc_hw_set_mainscaler(ctx);
+ fimc_hw_set_target_format(ctx);
+ fimc_hw_set_rotation(ctx);
+ fimc_hw_set_effect(ctx);
+ fimc_hw_set_out_dma(ctx);
+ if (fimc->drv_data->alpha_color)
+ fimc_hw_set_rgb_alpha(ctx);
+ fimc_hw_set_output_path(ctx);
+ }
+ fimc_hw_set_input_addr(fimc, &sf->addr);
+ fimc_hw_set_output_addr(fimc, &df->addr, -1);
+
+ fimc_activate_capture(ctx);
+ ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP);
+ fimc_hw_activate_input_dma(fimc, true);
+
+dma_unlock:
+ spin_unlock_irqrestore(&fimc->slock, flags);
+}
+
+static void fimc_job_abort(void *priv)
+{
+ fimc_m2m_shutdown(priv);
+}
+
+static int fimc_queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct fimc_ctx *ctx = vb2_get_drv_priv(vq);
+ struct fimc_frame *f;
+ int i;
+
+ f = ctx_get_frame(ctx, vq->type);
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+ /*
+ * Return number of non-contiguous planes (plane buffers)
+ * depending on the configured color format.
+ */
+ if (!f->fmt)
+ return -EINVAL;
+
+ *num_planes = f->fmt->memplanes;
+ for (i = 0; i < f->fmt->memplanes; i++)
+ sizes[i] = f->payload[i];
+ return 0;
+}
+
+static int fimc_buf_prepare(struct vb2_buffer *vb)
+{
+ struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct fimc_frame *frame;
+ int i;
+
+ frame = ctx_get_frame(ctx, vb->vb2_queue->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ for (i = 0; i < frame->fmt->memplanes; i++)
+ vb2_set_plane_payload(vb, i, frame->payload[i]);
+
+ return 0;
+}
+
+static void fimc_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static const struct vb2_ops fimc_qops = {
+ .queue_setup = fimc_queue_setup,
+ .buf_prepare = fimc_buf_prepare,
+ .buf_queue = fimc_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .stop_streaming = stop_streaming,
+ .start_streaming = start_streaming,
+};
+
+/*
+ * V4L2 ioctl handlers
+ */
+static int fimc_m2m_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+
+ __fimc_vidioc_querycap(&fimc->pdev->dev, cap);
+ return 0;
+}
+
+static int fimc_m2m_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct fimc_fmt *fmt;
+
+ fmt = fimc_find_format(NULL, NULL, get_m2m_fmt_flags(f->type),
+ f->index);
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->fourcc;
+ return 0;
+}
+
+static int fimc_m2m_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(fh);
+ struct fimc_frame *frame = ctx_get_frame(ctx, f->type);
+
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ __fimc_get_format(frame, f);
+ return 0;
+}
+
+static int fimc_try_fmt_mplane(struct fimc_ctx *ctx, struct v4l2_format *f)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ const struct fimc_variant *variant = fimc->variant;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct fimc_fmt *fmt;
+ u32 max_w, mod_x, mod_y;
+
+ if (!IS_M2M(f->type))
+ return -EINVAL;
+
+ fmt = fimc_find_format(&pix->pixelformat, NULL,
+ get_m2m_fmt_flags(f->type), 0);
+ if (WARN(fmt == NULL, "Pixel format lookup failed"))
+ return -EINVAL;
+
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = V4L2_FIELD_NONE;
+ else if (pix->field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ max_w = variant->pix_limit->scaler_dis_w;
+ mod_x = ffs(variant->min_inp_pixsize) - 1;
+ } else {
+ max_w = variant->pix_limit->out_rot_dis_w;
+ mod_x = ffs(variant->min_out_pixsize) - 1;
+ }
+
+ if (tiled_fmt(fmt)) {
+ mod_x = 6; /* 64 x 32 pixels tile */
+ mod_y = 5;
+ } else {
+ if (variant->min_vsize_align == 1)
+ mod_y = fimc_fmt_is_rgb(fmt->color) ? 0 : 1;
+ else
+ mod_y = ffs(variant->min_vsize_align) - 1;
+ }
+
+ v4l_bound_align_image(&pix->width, 16, max_w, mod_x,
+ &pix->height, 8, variant->pix_limit->scaler_dis_w, mod_y, 0);
+
+ fimc_adjust_mplane_format(fmt, pix->width, pix->height, &f->fmt.pix_mp);
+ return 0;
+}
+
+static int fimc_m2m_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(fh);
+ return fimc_try_fmt_mplane(ctx, f);
+}
+
+static void __set_frame_format(struct fimc_frame *frame, struct fimc_fmt *fmt,
+ struct v4l2_pix_format_mplane *pixm)
+{
+ int i;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ frame->bytesperline[i] = pixm->plane_fmt[i].bytesperline;
+ frame->payload[i] = pixm->plane_fmt[i].sizeimage;
+ }
+
+ frame->f_width = pixm->width;
+ frame->f_height = pixm->height;
+ frame->o_width = pixm->width;
+ frame->o_height = pixm->height;
+ frame->width = pixm->width;
+ frame->height = pixm->height;
+ frame->offs_h = 0;
+ frame->offs_v = 0;
+ frame->fmt = fmt;
+}
+
+static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(fh);
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_fmt *fmt;
+ struct vb2_queue *vq;
+ struct fimc_frame *frame;
+ int ret;
+
+ ret = fimc_try_fmt_mplane(ctx, f);
+ if (ret)
+ return ret;
+
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+
+ if (vb2_is_busy(vq)) {
+ v4l2_err(&fimc->m2m.vfd, "queue (%d) busy\n", f->type);
+ return -EBUSY;
+ }
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ frame = &ctx->s_frame;
+ else
+ frame = &ctx->d_frame;
+
+ fmt = fimc_find_format(&f->fmt.pix_mp.pixelformat, NULL,
+ get_m2m_fmt_flags(f->type), 0);
+ if (!fmt)
+ return -EINVAL;
+
+ __set_frame_format(frame, fmt, &f->fmt.pix_mp);
+
+ /* Update RGB Alpha control state and value range */
+ fimc_alpha_ctrl_update(ctx);
+
+ return 0;
+}
+
+static int fimc_m2m_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(fh);
+ struct fimc_frame *frame;
+
+ frame = ctx_get_frame(ctx, s->type);
+ if (IS_ERR(frame))
+ return PTR_ERR(frame);
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_COMPOSE:
+ s->r.left = frame->offs_h;
+ s->r.top = frame->offs_v;
+ s->r.width = frame->width;
+ s->r.height = frame->height;
+ break;
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = frame->o_width;
+ s->r.height = frame->o_height;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fimc_m2m_try_selection(struct fimc_ctx *ctx,
+ struct v4l2_selection *s)
+{
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_frame *f;
+ u32 min_size, halign, depth = 0;
+ int i;
+
+ if (s->r.top < 0 || s->r.left < 0) {
+ v4l2_err(&fimc->m2m.vfd,
+ "doesn't support negative values for top & left\n");
+ return -EINVAL;
+ }
+ if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ f = &ctx->d_frame;
+ if (s->target != V4L2_SEL_TGT_COMPOSE)
+ return -EINVAL;
+ } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ f = &ctx->s_frame;
+ if (s->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+ } else {
+ return -EINVAL;
+ }
+
+ min_size = (f == &ctx->s_frame) ?
+ fimc->variant->min_inp_pixsize : fimc->variant->min_out_pixsize;
+
+ /* Get pixel alignment constraints. */
+ if (fimc->variant->min_vsize_align == 1)
+ halign = fimc_fmt_is_rgb(f->fmt->color) ? 0 : 1;
+ else
+ halign = ffs(fimc->variant->min_vsize_align) - 1;
+
+ for (i = 0; i < f->fmt->memplanes; i++)
+ depth += f->fmt->depth[i];
+
+ v4l_bound_align_image(&s->r.width, min_size, f->o_width,
+ ffs(min_size) - 1,
+ &s->r.height, min_size, f->o_height,
+ halign, 64/(ALIGN(depth, 8)));
+
+ /* adjust left/top if cropping rectangle is out of bounds */
+ if (s->r.left + s->r.width > f->o_width)
+ s->r.left = f->o_width - s->r.width;
+ if (s->r.top + s->r.height > f->o_height)
+ s->r.top = f->o_height - s->r.height;
+
+ s->r.left = round_down(s->r.left, min_size);
+ s->r.top = round_down(s->r.top, fimc->variant->hor_offs_align);
+
+ dbg("l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d",
+ s->r.left, s->r.top, s->r.width, s->r.height,
+ f->f_width, f->f_height);
+
+ return 0;
+}
+
+static int fimc_m2m_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(fh);
+ struct fimc_dev *fimc = ctx->fimc_dev;
+ struct fimc_frame *f;
+ int ret;
+
+ ret = fimc_m2m_try_selection(ctx, s);
+ if (ret)
+ return ret;
+
+ f = (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ?
+ &ctx->s_frame : &ctx->d_frame;
+
+ /* Check to see if scaling ratio is within supported range */
+ if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ ret = fimc_check_scaler_ratio(ctx, s->r.width,
+ s->r.height, ctx->d_frame.width,
+ ctx->d_frame.height, ctx->rotation);
+ } else {
+ ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width,
+ ctx->s_frame.height, s->r.width,
+ s->r.height, ctx->rotation);
+ }
+ if (ret) {
+ v4l2_err(&fimc->m2m.vfd, "Out of scaler range\n");
+ return -EINVAL;
+ }
+
+ f->offs_h = s->r.left;
+ f->offs_v = s->r.top;
+ f->width = s->r.width;
+ f->height = s->r.height;
+
+ fimc_ctx_state_set(FIMC_PARAMS, ctx);
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = {
+ .vidioc_querycap = fimc_m2m_querycap,
+ .vidioc_enum_fmt_vid_cap = fimc_m2m_enum_fmt,
+ .vidioc_enum_fmt_vid_out = fimc_m2m_enum_fmt,
+ .vidioc_g_fmt_vid_cap_mplane = fimc_m2m_g_fmt_mplane,
+ .vidioc_g_fmt_vid_out_mplane = fimc_m2m_g_fmt_mplane,
+ .vidioc_try_fmt_vid_cap_mplane = fimc_m2m_try_fmt_mplane,
+ .vidioc_try_fmt_vid_out_mplane = fimc_m2m_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = fimc_m2m_s_fmt_mplane,
+ .vidioc_s_fmt_vid_out_mplane = fimc_m2m_s_fmt_mplane,
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+ .vidioc_g_selection = fimc_m2m_g_selection,
+ .vidioc_s_selection = fimc_m2m_s_selection,
+
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct fimc_ctx *ctx = priv;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &fimc_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->fimc_dev->lock;
+ src_vq->dev = &ctx->fimc_dev->pdev->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->ops = &fimc_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->fimc_dev->lock;
+ dst_vq->dev = &ctx->fimc_dev->pdev->dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+static int fimc_m2m_set_default_format(struct fimc_ctx *ctx)
+{
+ struct v4l2_pix_format_mplane pixm = {
+ .pixelformat = V4L2_PIX_FMT_RGB32,
+ .width = 800,
+ .height = 600,
+ .plane_fmt[0] = {
+ .bytesperline = 800 * 4,
+ .sizeimage = 800 * 4 * 600,
+ },
+ };
+ struct fimc_fmt *fmt;
+
+ fmt = fimc_find_format(&pixm.pixelformat, NULL, FMT_FLAGS_M2M, 0);
+ if (!fmt)
+ return -EINVAL;
+
+ __set_frame_format(&ctx->s_frame, fmt, &pixm);
+ __set_frame_format(&ctx->d_frame, fmt, &pixm);
+
+ return 0;
+}
+
+static int fimc_m2m_open(struct file *file)
+{
+ struct fimc_dev *fimc = video_drvdata(file);
+ struct fimc_ctx *ctx;
+ int ret = -EBUSY;
+
+ pr_debug("pid: %d, state: %#lx\n", task_pid_nr(current), fimc->state);
+
+ if (mutex_lock_interruptible(&fimc->lock))
+ return -ERESTARTSYS;
+ /*
+ * Don't allow simultaneous open() of the mem-to-mem and the
+ * capture video node that belong to same FIMC IP instance.
+ */
+ if (test_bit(ST_CAPT_BUSY, &fimc->state))
+ goto unlock;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+ v4l2_fh_init(&ctx->fh, &fimc->m2m.vfd);
+ ctx->fimc_dev = fimc;
+
+ /* Default color format */
+ ctx->s_frame.fmt = fimc_get_format(0);
+ ctx->d_frame.fmt = fimc_get_format(0);
+
+ ret = fimc_ctrls_create(ctx);
+ if (ret)
+ goto error_fh;
+
+ /* Use separate control handler per file handle */
+ ctx->fh.ctrl_handler = &ctx->ctrls.handler;
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+
+ /* Setup the device context for memory-to-memory mode */
+ ctx->state = FIMC_CTX_M2M;
+ ctx->flags = 0;
+ ctx->in_path = FIMC_IO_DMA;
+ ctx->out_path = FIMC_IO_DMA;
+ ctx->scaler.enabled = 1;
+
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ goto error_c;
+ }
+
+ if (fimc->m2m.refcnt++ == 0)
+ set_bit(ST_M2M_RUN, &fimc->state);
+
+ ret = fimc_m2m_set_default_format(ctx);
+ if (ret < 0)
+ goto error_m2m_ctx;
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+
+error_m2m_ctx:
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+error_c:
+ fimc_ctrls_delete(ctx);
+ v4l2_fh_del(&ctx->fh);
+error_fh:
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+unlock:
+ mutex_unlock(&fimc->lock);
+ return ret;
+}
+
+static int fimc_m2m_release(struct file *file)
+{
+ struct fimc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct fimc_dev *fimc = ctx->fimc_dev;
+
+ dbg("pid: %d, state: 0x%lx, refcnt= %d",
+ task_pid_nr(current), fimc->state, fimc->m2m.refcnt);
+
+ mutex_lock(&fimc->lock);
+
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+ fimc_ctrls_delete(ctx);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+
+ if (--fimc->m2m.refcnt <= 0)
+ clear_bit(ST_M2M_RUN, &fimc->state);
+ kfree(ctx);
+
+ mutex_unlock(&fimc->lock);
+ return 0;
+}
+
+static const struct v4l2_file_operations fimc_m2m_fops = {
+ .owner = THIS_MODULE,
+ .open = fimc_m2m_open,
+ .release = fimc_m2m_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static const struct v4l2_m2m_ops m2m_ops = {
+ .device_run = fimc_device_run,
+ .job_abort = fimc_job_abort,
+};
+
+int fimc_register_m2m_device(struct fimc_dev *fimc,
+ struct v4l2_device *v4l2_dev)
+{
+ struct video_device *vfd = &fimc->m2m.vfd;
+ int ret;
+
+ fimc->v4l2_dev = v4l2_dev;
+
+ memset(vfd, 0, sizeof(*vfd));
+ vfd->fops = &fimc_m2m_fops;
+ vfd->ioctl_ops = &fimc_m2m_ioctl_ops;
+ vfd->v4l2_dev = v4l2_dev;
+ vfd->minor = -1;
+ vfd->release = video_device_release_empty;
+ vfd->lock = &fimc->lock;
+ vfd->vfl_dir = VFL_DIR_M2M;
+ vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+ set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags);
+
+ snprintf(vfd->name, sizeof(vfd->name), "fimc.%d.m2m", fimc->id);
+ video_set_drvdata(vfd, fimc);
+
+ fimc->m2m.m2m_dev = v4l2_m2m_init(&m2m_ops);
+ if (IS_ERR(fimc->m2m.m2m_dev)) {
+ v4l2_err(v4l2_dev, "failed to initialize v4l2-m2m device\n");
+ return PTR_ERR(fimc->m2m.m2m_dev);
+ }
+
+ ret = media_entity_pads_init(&vfd->entity, 0, NULL);
+ if (ret)
+ goto err_me;
+
+ ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
+ if (ret)
+ goto err_vd;
+
+ v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
+ vfd->name, video_device_node_name(vfd));
+ return 0;
+
+err_vd:
+ media_entity_cleanup(&vfd->entity);
+err_me:
+ v4l2_m2m_release(fimc->m2m.m2m_dev);
+ return ret;
+}
+
+void fimc_unregister_m2m_device(struct fimc_dev *fimc)
+{
+ if (!fimc)
+ return;
+
+ if (fimc->m2m.m2m_dev)
+ v4l2_m2m_release(fimc->m2m.m2m_dev);
+
+ if (video_is_registered(&fimc->m2m.vfd)) {
+ video_unregister_device(&fimc->m2m.vfd);
+ media_entity_cleanup(&fimc->m2m.vfd.entity);
+ }
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-reg.c b/drivers/media/platform/samsung/exynos4-is/fimc-reg.c
new file mode 100644
index 0000000000..95165a2cc7
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-reg.c
@@ -0,0 +1,846 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Register interface file for Samsung Camera Interface (FIMC) driver
+ *
+ * Copyright (C) 2010 - 2013 Samsung Electronics Co., Ltd.
+ * Sylwester Nawrocki <s.nawrocki@samsung.com>
+*/
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/regmap.h>
+
+#include <media/drv-intf/exynos-fimc.h>
+#include "media-dev.h"
+
+#include "fimc-reg.h"
+#include "fimc-core.h"
+
+void fimc_hw_reset(struct fimc_dev *dev)
+{
+ u32 cfg;
+
+ cfg = readl(dev->regs + FIMC_REG_CISRCFMT);
+ cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT;
+ writel(cfg, dev->regs + FIMC_REG_CISRCFMT);
+
+ /* Software reset. */
+ cfg = readl(dev->regs + FIMC_REG_CIGCTRL);
+ cfg |= (FIMC_REG_CIGCTRL_SWRST | FIMC_REG_CIGCTRL_IRQ_LEVEL);
+ writel(cfg, dev->regs + FIMC_REG_CIGCTRL);
+ udelay(10);
+
+ cfg = readl(dev->regs + FIMC_REG_CIGCTRL);
+ cfg &= ~FIMC_REG_CIGCTRL_SWRST;
+ writel(cfg, dev->regs + FIMC_REG_CIGCTRL);
+
+ if (dev->drv_data->out_buf_count > 4)
+ fimc_hw_set_dma_seq(dev, 0xF);
+}
+
+static u32 fimc_hw_get_in_flip(struct fimc_ctx *ctx)
+{
+ u32 flip = FIMC_REG_MSCTRL_FLIP_NORMAL;
+
+ if (ctx->hflip)
+ flip = FIMC_REG_MSCTRL_FLIP_Y_MIRROR;
+ if (ctx->vflip)
+ flip = FIMC_REG_MSCTRL_FLIP_X_MIRROR;
+
+ if (ctx->rotation <= 90)
+ return flip;
+
+ return (flip ^ FIMC_REG_MSCTRL_FLIP_180) & FIMC_REG_MSCTRL_FLIP_180;
+}
+
+static u32 fimc_hw_get_target_flip(struct fimc_ctx *ctx)
+{
+ u32 flip = FIMC_REG_CITRGFMT_FLIP_NORMAL;
+
+ if (ctx->hflip)
+ flip |= FIMC_REG_CITRGFMT_FLIP_Y_MIRROR;
+ if (ctx->vflip)
+ flip |= FIMC_REG_CITRGFMT_FLIP_X_MIRROR;
+
+ if (ctx->rotation <= 90)
+ return flip;
+
+ return (flip ^ FIMC_REG_CITRGFMT_FLIP_180) & FIMC_REG_CITRGFMT_FLIP_180;
+}
+
+void fimc_hw_set_rotation(struct fimc_ctx *ctx)
+{
+ u32 cfg, flip;
+ struct fimc_dev *dev = ctx->fimc_dev;
+
+ cfg = readl(dev->regs + FIMC_REG_CITRGFMT);
+ cfg &= ~(FIMC_REG_CITRGFMT_INROT90 | FIMC_REG_CITRGFMT_OUTROT90 |
+ FIMC_REG_CITRGFMT_FLIP_180);
+
+ /*
+ * The input and output rotator cannot work simultaneously.
+ * Use the output rotator in output DMA mode or the input rotator
+ * in direct fifo output mode.
+ */
+ if (ctx->rotation == 90 || ctx->rotation == 270) {
+ if (ctx->out_path == FIMC_IO_LCDFIFO)
+ cfg |= FIMC_REG_CITRGFMT_INROT90;
+ else
+ cfg |= FIMC_REG_CITRGFMT_OUTROT90;
+ }
+
+ if (ctx->out_path == FIMC_IO_DMA) {
+ cfg |= fimc_hw_get_target_flip(ctx);
+ writel(cfg, dev->regs + FIMC_REG_CITRGFMT);
+ } else {
+ /* LCD FIFO path */
+ flip = readl(dev->regs + FIMC_REG_MSCTRL);
+ flip &= ~FIMC_REG_MSCTRL_FLIP_MASK;
+ flip |= fimc_hw_get_in_flip(ctx);
+ writel(flip, dev->regs + FIMC_REG_MSCTRL);
+ }
+}
+
+void fimc_hw_set_target_format(struct fimc_ctx *ctx)
+{
+ u32 cfg;
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->d_frame;
+
+ dbg("w= %d, h= %d color: %d", frame->width,
+ frame->height, frame->fmt->color);
+
+ cfg = readl(dev->regs + FIMC_REG_CITRGFMT);
+ cfg &= ~(FIMC_REG_CITRGFMT_FMT_MASK | FIMC_REG_CITRGFMT_HSIZE_MASK |
+ FIMC_REG_CITRGFMT_VSIZE_MASK);
+
+ switch (frame->fmt->color) {
+ case FIMC_FMT_RGB444...FIMC_FMT_RGB888:
+ cfg |= FIMC_REG_CITRGFMT_RGB;
+ break;
+ case FIMC_FMT_YCBCR420:
+ cfg |= FIMC_REG_CITRGFMT_YCBCR420;
+ break;
+ case FIMC_FMT_YCBYCR422...FIMC_FMT_CRYCBY422:
+ if (frame->fmt->colplanes == 1)
+ cfg |= FIMC_REG_CITRGFMT_YCBCR422_1P;
+ else
+ cfg |= FIMC_REG_CITRGFMT_YCBCR422;
+ break;
+ default:
+ break;
+ }
+
+ if (ctx->rotation == 90 || ctx->rotation == 270)
+ cfg |= (frame->height << 16) | frame->width;
+ else
+ cfg |= (frame->width << 16) | frame->height;
+
+ writel(cfg, dev->regs + FIMC_REG_CITRGFMT);
+
+ cfg = readl(dev->regs + FIMC_REG_CITAREA);
+ cfg &= ~FIMC_REG_CITAREA_MASK;
+ cfg |= (frame->width * frame->height);
+ writel(cfg, dev->regs + FIMC_REG_CITAREA);
+}
+
+static void fimc_hw_set_out_dma_size(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->d_frame;
+ u32 cfg;
+
+ cfg = (frame->f_height << 16) | frame->f_width;
+ writel(cfg, dev->regs + FIMC_REG_ORGOSIZE);
+
+ /* Select color space conversion equation (HD/SD size).*/
+ cfg = readl(dev->regs + FIMC_REG_CIGCTRL);
+ if (frame->f_width >= 1280) /* HD */
+ cfg |= FIMC_REG_CIGCTRL_CSC_ITU601_709;
+ else /* SD */
+ cfg &= ~FIMC_REG_CIGCTRL_CSC_ITU601_709;
+ writel(cfg, dev->regs + FIMC_REG_CIGCTRL);
+
+}
+
+void fimc_hw_set_out_dma(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->d_frame;
+ struct fimc_dma_offset *offset = &frame->dma_offset;
+ struct fimc_fmt *fmt = frame->fmt;
+ u32 cfg;
+
+ /* Set the input dma offsets. */
+ cfg = (offset->y_v << 16) | offset->y_h;
+ writel(cfg, dev->regs + FIMC_REG_CIOYOFF);
+
+ cfg = (offset->cb_v << 16) | offset->cb_h;
+ writel(cfg, dev->regs + FIMC_REG_CIOCBOFF);
+
+ cfg = (offset->cr_v << 16) | offset->cr_h;
+ writel(cfg, dev->regs + FIMC_REG_CIOCROFF);
+
+ fimc_hw_set_out_dma_size(ctx);
+
+ /* Configure chroma components order. */
+ cfg = readl(dev->regs + FIMC_REG_CIOCTRL);
+
+ cfg &= ~(FIMC_REG_CIOCTRL_ORDER2P_MASK |
+ FIMC_REG_CIOCTRL_ORDER422_MASK |
+ FIMC_REG_CIOCTRL_YCBCR_PLANE_MASK |
+ FIMC_REG_CIOCTRL_RGB16FMT_MASK);
+
+ if (fmt->colplanes == 1)
+ cfg |= ctx->out_order_1p;
+ else if (fmt->colplanes == 2)
+ cfg |= ctx->out_order_2p | FIMC_REG_CIOCTRL_YCBCR_2PLANE;
+ else if (fmt->colplanes == 3)
+ cfg |= FIMC_REG_CIOCTRL_YCBCR_3PLANE;
+
+ if (fmt->color == FIMC_FMT_RGB565)
+ cfg |= FIMC_REG_CIOCTRL_RGB565;
+ else if (fmt->color == FIMC_FMT_RGB555)
+ cfg |= FIMC_REG_CIOCTRL_ARGB1555;
+ else if (fmt->color == FIMC_FMT_RGB444)
+ cfg |= FIMC_REG_CIOCTRL_ARGB4444;
+
+ writel(cfg, dev->regs + FIMC_REG_CIOCTRL);
+}
+
+static void fimc_hw_en_autoload(struct fimc_dev *dev, int enable)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_ORGISIZE);
+ if (enable)
+ cfg |= FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN;
+ else
+ cfg &= ~FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN;
+ writel(cfg, dev->regs + FIMC_REG_ORGISIZE);
+}
+
+void fimc_hw_en_lastirq(struct fimc_dev *dev, int enable)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_CIOCTRL);
+ if (enable)
+ cfg |= FIMC_REG_CIOCTRL_LASTIRQ_ENABLE;
+ else
+ cfg &= ~FIMC_REG_CIOCTRL_LASTIRQ_ENABLE;
+ writel(cfg, dev->regs + FIMC_REG_CIOCTRL);
+}
+
+void fimc_hw_set_prescaler(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_scaler *sc = &ctx->scaler;
+ u32 cfg, shfactor;
+
+ shfactor = 10 - (sc->hfactor + sc->vfactor);
+ cfg = shfactor << 28;
+
+ cfg |= (sc->pre_hratio << 16) | sc->pre_vratio;
+ writel(cfg, dev->regs + FIMC_REG_CISCPRERATIO);
+
+ cfg = (sc->pre_dst_width << 16) | sc->pre_dst_height;
+ writel(cfg, dev->regs + FIMC_REG_CISCPREDST);
+}
+
+static void fimc_hw_set_scaler(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_scaler *sc = &ctx->scaler;
+ struct fimc_frame *src_frame = &ctx->s_frame;
+ struct fimc_frame *dst_frame = &ctx->d_frame;
+
+ u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL);
+
+ cfg &= ~(FIMC_REG_CISCCTRL_CSCR2Y_WIDE | FIMC_REG_CISCCTRL_CSCY2R_WIDE |
+ FIMC_REG_CISCCTRL_SCALEUP_H | FIMC_REG_CISCCTRL_SCALEUP_V |
+ FIMC_REG_CISCCTRL_SCALERBYPASS | FIMC_REG_CISCCTRL_ONE2ONE |
+ FIMC_REG_CISCCTRL_INRGB_FMT_MASK | FIMC_REG_CISCCTRL_OUTRGB_FMT_MASK |
+ FIMC_REG_CISCCTRL_INTERLACE | FIMC_REG_CISCCTRL_RGB_EXT);
+
+ if (!(ctx->flags & FIMC_COLOR_RANGE_NARROW))
+ cfg |= (FIMC_REG_CISCCTRL_CSCR2Y_WIDE |
+ FIMC_REG_CISCCTRL_CSCY2R_WIDE);
+
+ if (!sc->enabled)
+ cfg |= FIMC_REG_CISCCTRL_SCALERBYPASS;
+
+ if (sc->scaleup_h)
+ cfg |= FIMC_REG_CISCCTRL_SCALEUP_H;
+
+ if (sc->scaleup_v)
+ cfg |= FIMC_REG_CISCCTRL_SCALEUP_V;
+
+ if (sc->copy_mode)
+ cfg |= FIMC_REG_CISCCTRL_ONE2ONE;
+
+ if (ctx->in_path == FIMC_IO_DMA) {
+ switch (src_frame->fmt->color) {
+ case FIMC_FMT_RGB565:
+ cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB565;
+ break;
+ case FIMC_FMT_RGB666:
+ cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB666;
+ break;
+ case FIMC_FMT_RGB888:
+ cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB888;
+ break;
+ }
+ }
+
+ if (ctx->out_path == FIMC_IO_DMA) {
+ u32 color = dst_frame->fmt->color;
+
+ if (color >= FIMC_FMT_RGB444 && color <= FIMC_FMT_RGB565)
+ cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB565;
+ else if (color == FIMC_FMT_RGB666)
+ cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB666;
+ else if (color == FIMC_FMT_RGB888)
+ cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888;
+ } else {
+ cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888;
+
+ if (ctx->flags & FIMC_SCAN_MODE_INTERLACED)
+ cfg |= FIMC_REG_CISCCTRL_INTERLACE;
+ }
+
+ writel(cfg, dev->regs + FIMC_REG_CISCCTRL);
+}
+
+void fimc_hw_set_mainscaler(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ const struct fimc_variant *variant = dev->variant;
+ struct fimc_scaler *sc = &ctx->scaler;
+ u32 cfg;
+
+ dbg("main_hratio= 0x%X main_vratio= 0x%X",
+ sc->main_hratio, sc->main_vratio);
+
+ fimc_hw_set_scaler(ctx);
+
+ cfg = readl(dev->regs + FIMC_REG_CISCCTRL);
+ cfg &= ~(FIMC_REG_CISCCTRL_MHRATIO_MASK |
+ FIMC_REG_CISCCTRL_MVRATIO_MASK);
+
+ if (variant->has_mainscaler_ext) {
+ cfg |= FIMC_REG_CISCCTRL_MHRATIO_EXT(sc->main_hratio);
+ cfg |= FIMC_REG_CISCCTRL_MVRATIO_EXT(sc->main_vratio);
+ writel(cfg, dev->regs + FIMC_REG_CISCCTRL);
+
+ cfg = readl(dev->regs + FIMC_REG_CIEXTEN);
+
+ cfg &= ~(FIMC_REG_CIEXTEN_MVRATIO_EXT_MASK |
+ FIMC_REG_CIEXTEN_MHRATIO_EXT_MASK);
+ cfg |= FIMC_REG_CIEXTEN_MHRATIO_EXT(sc->main_hratio);
+ cfg |= FIMC_REG_CIEXTEN_MVRATIO_EXT(sc->main_vratio);
+ writel(cfg, dev->regs + FIMC_REG_CIEXTEN);
+ } else {
+ cfg |= FIMC_REG_CISCCTRL_MHRATIO(sc->main_hratio);
+ cfg |= FIMC_REG_CISCCTRL_MVRATIO(sc->main_vratio);
+ writel(cfg, dev->regs + FIMC_REG_CISCCTRL);
+ }
+}
+
+void fimc_hw_enable_capture(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ u32 cfg;
+
+ cfg = readl(dev->regs + FIMC_REG_CIIMGCPT);
+ cfg |= FIMC_REG_CIIMGCPT_CPT_FREN_ENABLE;
+
+ if (ctx->scaler.enabled)
+ cfg |= FIMC_REG_CIIMGCPT_IMGCPTEN_SC;
+ else
+ cfg &= FIMC_REG_CIIMGCPT_IMGCPTEN_SC;
+
+ cfg |= FIMC_REG_CIIMGCPT_IMGCPTEN;
+ writel(cfg, dev->regs + FIMC_REG_CIIMGCPT);
+}
+
+void fimc_hw_disable_capture(struct fimc_dev *dev)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_CIIMGCPT);
+ cfg &= ~(FIMC_REG_CIIMGCPT_IMGCPTEN |
+ FIMC_REG_CIIMGCPT_IMGCPTEN_SC);
+ writel(cfg, dev->regs + FIMC_REG_CIIMGCPT);
+}
+
+void fimc_hw_set_effect(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_effect *effect = &ctx->effect;
+ u32 cfg = 0;
+
+ if (effect->type != FIMC_REG_CIIMGEFF_FIN_BYPASS) {
+ cfg |= FIMC_REG_CIIMGEFF_IE_SC_AFTER |
+ FIMC_REG_CIIMGEFF_IE_ENABLE;
+ cfg |= effect->type;
+ if (effect->type == FIMC_REG_CIIMGEFF_FIN_ARBITRARY)
+ cfg |= (effect->pat_cb << 13) | effect->pat_cr;
+ }
+
+ writel(cfg, dev->regs + FIMC_REG_CIIMGEFF);
+}
+
+void fimc_hw_set_rgb_alpha(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->d_frame;
+ u32 cfg;
+
+ if (!(frame->fmt->flags & FMT_HAS_ALPHA))
+ return;
+
+ cfg = readl(dev->regs + FIMC_REG_CIOCTRL);
+ cfg &= ~FIMC_REG_CIOCTRL_ALPHA_OUT_MASK;
+ cfg |= (frame->alpha << 4);
+ writel(cfg, dev->regs + FIMC_REG_CIOCTRL);
+}
+
+static void fimc_hw_set_in_dma_size(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->s_frame;
+ u32 cfg_o = 0;
+ u32 cfg_r = 0;
+
+ if (FIMC_IO_LCDFIFO == ctx->out_path)
+ cfg_r |= FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN;
+
+ cfg_o |= (frame->f_height << 16) | frame->f_width;
+ cfg_r |= (frame->height << 16) | frame->width;
+
+ writel(cfg_o, dev->regs + FIMC_REG_ORGISIZE);
+ writel(cfg_r, dev->regs + FIMC_REG_CIREAL_ISIZE);
+}
+
+void fimc_hw_set_in_dma(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+ struct fimc_frame *frame = &ctx->s_frame;
+ struct fimc_dma_offset *offset = &frame->dma_offset;
+ u32 cfg;
+
+ /* Set the pixel offsets. */
+ cfg = (offset->y_v << 16) | offset->y_h;
+ writel(cfg, dev->regs + FIMC_REG_CIIYOFF);
+
+ cfg = (offset->cb_v << 16) | offset->cb_h;
+ writel(cfg, dev->regs + FIMC_REG_CIICBOFF);
+
+ cfg = (offset->cr_v << 16) | offset->cr_h;
+ writel(cfg, dev->regs + FIMC_REG_CIICROFF);
+
+ /* Input original and real size. */
+ fimc_hw_set_in_dma_size(ctx);
+
+ /* Use DMA autoload only in FIFO mode. */
+ fimc_hw_en_autoload(dev, ctx->out_path == FIMC_IO_LCDFIFO);
+
+ /* Set the input DMA to process single frame only. */
+ cfg = readl(dev->regs + FIMC_REG_MSCTRL);
+ cfg &= ~(FIMC_REG_MSCTRL_INFORMAT_MASK
+ | FIMC_REG_MSCTRL_IN_BURST_COUNT_MASK
+ | FIMC_REG_MSCTRL_INPUT_MASK
+ | FIMC_REG_MSCTRL_C_INT_IN_MASK
+ | FIMC_REG_MSCTRL_2P_IN_ORDER_MASK
+ | FIMC_REG_MSCTRL_ORDER422_MASK);
+
+ cfg |= (FIMC_REG_MSCTRL_IN_BURST_COUNT(4)
+ | FIMC_REG_MSCTRL_INPUT_MEMORY
+ | FIMC_REG_MSCTRL_FIFO_CTRL_FULL);
+
+ switch (frame->fmt->color) {
+ case FIMC_FMT_RGB565...FIMC_FMT_RGB888:
+ cfg |= FIMC_REG_MSCTRL_INFORMAT_RGB;
+ break;
+ case FIMC_FMT_YCBCR420:
+ cfg |= FIMC_REG_MSCTRL_INFORMAT_YCBCR420;
+
+ if (frame->fmt->colplanes == 2)
+ cfg |= ctx->in_order_2p | FIMC_REG_MSCTRL_C_INT_IN_2PLANE;
+ else
+ cfg |= FIMC_REG_MSCTRL_C_INT_IN_3PLANE;
+
+ break;
+ case FIMC_FMT_YCBYCR422...FIMC_FMT_CRYCBY422:
+ if (frame->fmt->colplanes == 1) {
+ cfg |= ctx->in_order_1p
+ | FIMC_REG_MSCTRL_INFORMAT_YCBCR422_1P;
+ } else {
+ cfg |= FIMC_REG_MSCTRL_INFORMAT_YCBCR422;
+
+ if (frame->fmt->colplanes == 2)
+ cfg |= ctx->in_order_2p
+ | FIMC_REG_MSCTRL_C_INT_IN_2PLANE;
+ else
+ cfg |= FIMC_REG_MSCTRL_C_INT_IN_3PLANE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ writel(cfg, dev->regs + FIMC_REG_MSCTRL);
+
+ /* Input/output DMA linear/tiled mode. */
+ cfg = readl(dev->regs + FIMC_REG_CIDMAPARAM);
+ cfg &= ~FIMC_REG_CIDMAPARAM_TILE_MASK;
+
+ if (tiled_fmt(ctx->s_frame.fmt))
+ cfg |= FIMC_REG_CIDMAPARAM_R_64X32;
+
+ if (tiled_fmt(ctx->d_frame.fmt))
+ cfg |= FIMC_REG_CIDMAPARAM_W_64X32;
+
+ writel(cfg, dev->regs + FIMC_REG_CIDMAPARAM);
+}
+
+
+void fimc_hw_set_input_path(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+
+ u32 cfg = readl(dev->regs + FIMC_REG_MSCTRL);
+ cfg &= ~FIMC_REG_MSCTRL_INPUT_MASK;
+
+ if (ctx->in_path == FIMC_IO_DMA)
+ cfg |= FIMC_REG_MSCTRL_INPUT_MEMORY;
+ else
+ cfg |= FIMC_REG_MSCTRL_INPUT_EXTCAM;
+
+ writel(cfg, dev->regs + FIMC_REG_MSCTRL);
+}
+
+void fimc_hw_set_output_path(struct fimc_ctx *ctx)
+{
+ struct fimc_dev *dev = ctx->fimc_dev;
+
+ u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL);
+ cfg &= ~FIMC_REG_CISCCTRL_LCDPATHEN_FIFO;
+ if (ctx->out_path == FIMC_IO_LCDFIFO)
+ cfg |= FIMC_REG_CISCCTRL_LCDPATHEN_FIFO;
+ writel(cfg, dev->regs + FIMC_REG_CISCCTRL);
+}
+
+void fimc_hw_set_input_addr(struct fimc_dev *dev, struct fimc_addr *addr)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_CIREAL_ISIZE);
+ cfg |= FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS;
+ writel(cfg, dev->regs + FIMC_REG_CIREAL_ISIZE);
+
+ writel(addr->y, dev->regs + FIMC_REG_CIIYSA(0));
+ writel(addr->cb, dev->regs + FIMC_REG_CIICBSA(0));
+ writel(addr->cr, dev->regs + FIMC_REG_CIICRSA(0));
+
+ cfg &= ~FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS;
+ writel(cfg, dev->regs + FIMC_REG_CIREAL_ISIZE);
+}
+
+void fimc_hw_set_output_addr(struct fimc_dev *dev,
+ struct fimc_addr *addr, int index)
+{
+ int i = (index == -1) ? 0 : index;
+ do {
+ writel(addr->y, dev->regs + FIMC_REG_CIOYSA(i));
+ writel(addr->cb, dev->regs + FIMC_REG_CIOCBSA(i));
+ writel(addr->cr, dev->regs + FIMC_REG_CIOCRSA(i));
+ dbg("dst_buf[%d]: 0x%X, cb: 0x%X, cr: 0x%X",
+ i, addr->y, addr->cb, addr->cr);
+ } while (index == -1 && ++i < FIMC_MAX_OUT_BUFS);
+}
+
+int fimc_hw_set_camera_polarity(struct fimc_dev *fimc,
+ struct fimc_source_info *cam)
+{
+ u32 cfg = readl(fimc->regs + FIMC_REG_CIGCTRL);
+
+ cfg &= ~(FIMC_REG_CIGCTRL_INVPOLPCLK | FIMC_REG_CIGCTRL_INVPOLVSYNC |
+ FIMC_REG_CIGCTRL_INVPOLHREF | FIMC_REG_CIGCTRL_INVPOLHSYNC |
+ FIMC_REG_CIGCTRL_INVPOLFIELD);
+
+ if (cam->flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= FIMC_REG_CIGCTRL_INVPOLPCLK;
+
+ if (cam->flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ cfg |= FIMC_REG_CIGCTRL_INVPOLVSYNC;
+
+ if (cam->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ cfg |= FIMC_REG_CIGCTRL_INVPOLHREF;
+
+ if (cam->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+ cfg |= FIMC_REG_CIGCTRL_INVPOLHSYNC;
+
+ if (cam->flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= FIMC_REG_CIGCTRL_INVPOLFIELD;
+
+ writel(cfg, fimc->regs + FIMC_REG_CIGCTRL);
+
+ return 0;
+}
+
+struct mbus_pixfmt_desc {
+ u32 pixelcode;
+ u32 cisrcfmt;
+ u16 bus_width;
+};
+
+static const struct mbus_pixfmt_desc pix_desc[] = {
+ { MEDIA_BUS_FMT_YUYV8_2X8, FIMC_REG_CISRCFMT_ORDER422_YCBYCR, 8 },
+ { MEDIA_BUS_FMT_YVYU8_2X8, FIMC_REG_CISRCFMT_ORDER422_YCRYCB, 8 },
+ { MEDIA_BUS_FMT_VYUY8_2X8, FIMC_REG_CISRCFMT_ORDER422_CRYCBY, 8 },
+ { MEDIA_BUS_FMT_UYVY8_2X8, FIMC_REG_CISRCFMT_ORDER422_CBYCRY, 8 },
+};
+
+int fimc_hw_set_camera_source(struct fimc_dev *fimc,
+ struct fimc_source_info *source)
+{
+ struct fimc_vid_cap *vc = &fimc->vid_cap;
+ struct fimc_frame *f = &vc->ctx->s_frame;
+ u32 bus_width, cfg = 0;
+ int i;
+
+ switch (source->fimc_bus_type) {
+ case FIMC_BUS_TYPE_ITU_601:
+ case FIMC_BUS_TYPE_ITU_656:
+ if (fimc_fmt_is_user_defined(f->fmt->color)) {
+ cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT;
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(pix_desc); i++) {
+ if (vc->ci_fmt.code == pix_desc[i].pixelcode) {
+ cfg = pix_desc[i].cisrcfmt;
+ bus_width = pix_desc[i].bus_width;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(pix_desc)) {
+ v4l2_err(&vc->ve.vdev,
+ "Camera color format not supported: %d\n",
+ vc->ci_fmt.code);
+ return -EINVAL;
+ }
+
+ if (source->fimc_bus_type == FIMC_BUS_TYPE_ITU_601) {
+ if (bus_width == 8)
+ cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT;
+ else if (bus_width == 16)
+ cfg |= FIMC_REG_CISRCFMT_ITU601_16BIT;
+ } /* else defaults to ITU-R BT.656 8-bit */
+ break;
+ case FIMC_BUS_TYPE_MIPI_CSI2:
+ if (fimc_fmt_is_user_defined(f->fmt->color))
+ cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT;
+ break;
+ default:
+ case FIMC_BUS_TYPE_ISP_WRITEBACK:
+ /* Anything to do here ? */
+ break;
+ }
+
+ cfg |= (f->o_width << 16) | f->o_height;
+ writel(cfg, fimc->regs + FIMC_REG_CISRCFMT);
+ return 0;
+}
+
+void fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f)
+{
+ u32 hoff2, voff2;
+
+ u32 cfg = readl(fimc->regs + FIMC_REG_CIWDOFST);
+
+ cfg &= ~(FIMC_REG_CIWDOFST_HOROFF_MASK | FIMC_REG_CIWDOFST_VEROFF_MASK);
+ cfg |= FIMC_REG_CIWDOFST_OFF_EN |
+ (f->offs_h << 16) | f->offs_v;
+
+ writel(cfg, fimc->regs + FIMC_REG_CIWDOFST);
+
+ /* See CIWDOFSTn register description in the datasheet for details. */
+ hoff2 = f->o_width - f->width - f->offs_h;
+ voff2 = f->o_height - f->height - f->offs_v;
+ cfg = (hoff2 << 16) | voff2;
+ writel(cfg, fimc->regs + FIMC_REG_CIWDOFST2);
+}
+
+int fimc_hw_set_camera_type(struct fimc_dev *fimc,
+ struct fimc_source_info *source)
+{
+ struct fimc_vid_cap *vid_cap = &fimc->vid_cap;
+ u32 csis_data_alignment = 32;
+ u32 cfg, tmp;
+
+ cfg = readl(fimc->regs + FIMC_REG_CIGCTRL);
+
+ /* Select ITU B interface, disable Writeback path and test pattern. */
+ cfg &= ~(FIMC_REG_CIGCTRL_TESTPAT_MASK | FIMC_REG_CIGCTRL_SELCAM_ITU_A |
+ FIMC_REG_CIGCTRL_SELCAM_MIPI | FIMC_REG_CIGCTRL_CAMIF_SELWB |
+ FIMC_REG_CIGCTRL_SELCAM_MIPI_A | FIMC_REG_CIGCTRL_CAM_JPEG |
+ FIMC_REG_CIGCTRL_SELWB_A);
+
+ switch (source->fimc_bus_type) {
+ case FIMC_BUS_TYPE_MIPI_CSI2:
+ cfg |= FIMC_REG_CIGCTRL_SELCAM_MIPI;
+
+ if (source->mux_id == 0)
+ cfg |= FIMC_REG_CIGCTRL_SELCAM_MIPI_A;
+
+ /* TODO: add remaining supported formats. */
+ switch (vid_cap->ci_fmt.code) {
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ tmp = FIMC_REG_CSIIMGFMT_YCBCR422_8BIT;
+ break;
+ case MEDIA_BUS_FMT_JPEG_1X8:
+ case MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8:
+ tmp = FIMC_REG_CSIIMGFMT_USER(1);
+ cfg |= FIMC_REG_CIGCTRL_CAM_JPEG;
+ break;
+ default:
+ v4l2_err(&vid_cap->ve.vdev,
+ "Not supported camera pixel format: %#x\n",
+ vid_cap->ci_fmt.code);
+ return -EINVAL;
+ }
+ tmp |= (csis_data_alignment == 32) << 8;
+
+ writel(tmp, fimc->regs + FIMC_REG_CSIIMGFMT);
+ break;
+ case FIMC_BUS_TYPE_ITU_601...FIMC_BUS_TYPE_ITU_656:
+ if (source->mux_id == 0) /* ITU-A, ITU-B: 0, 1 */
+ cfg |= FIMC_REG_CIGCTRL_SELCAM_ITU_A;
+ if (vid_cap->ci_fmt.code == MEDIA_BUS_FMT_JPEG_1X8)
+ cfg |= FIMC_REG_CIGCTRL_CAM_JPEG;
+ break;
+ case FIMC_BUS_TYPE_LCD_WRITEBACK_A:
+ cfg |= FIMC_REG_CIGCTRL_CAMIF_SELWB;
+ fallthrough;
+ case FIMC_BUS_TYPE_ISP_WRITEBACK:
+ if (fimc->variant->has_isp_wb)
+ cfg |= FIMC_REG_CIGCTRL_CAMIF_SELWB;
+ else
+ WARN_ONCE(1, "ISP Writeback input is not supported\n");
+ break;
+ default:
+ v4l2_err(&vid_cap->ve.vdev,
+ "Invalid FIMC bus type selected: %d\n",
+ source->fimc_bus_type);
+ return -EINVAL;
+ }
+ writel(cfg, fimc->regs + FIMC_REG_CIGCTRL);
+
+ return 0;
+}
+
+void fimc_hw_clear_irq(struct fimc_dev *dev)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_CIGCTRL);
+ cfg |= FIMC_REG_CIGCTRL_IRQ_CLR;
+ writel(cfg, dev->regs + FIMC_REG_CIGCTRL);
+}
+
+void fimc_hw_enable_scaler(struct fimc_dev *dev, bool on)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL);
+ if (on)
+ cfg |= FIMC_REG_CISCCTRL_SCALERSTART;
+ else
+ cfg &= ~FIMC_REG_CISCCTRL_SCALERSTART;
+ writel(cfg, dev->regs + FIMC_REG_CISCCTRL);
+}
+
+void fimc_hw_activate_input_dma(struct fimc_dev *dev, bool on)
+{
+ u32 cfg = readl(dev->regs + FIMC_REG_MSCTRL);
+ if (on)
+ cfg |= FIMC_REG_MSCTRL_ENVID;
+ else
+ cfg &= ~FIMC_REG_MSCTRL_ENVID;
+ writel(cfg, dev->regs + FIMC_REG_MSCTRL);
+}
+
+/* Return an index to the buffer actually being written. */
+s32 fimc_hw_get_frame_index(struct fimc_dev *dev)
+{
+ s32 reg;
+
+ if (dev->drv_data->cistatus2) {
+ reg = readl(dev->regs + FIMC_REG_CISTATUS2) & 0x3f;
+ return reg - 1;
+ }
+
+ reg = readl(dev->regs + FIMC_REG_CISTATUS);
+
+ return (reg & FIMC_REG_CISTATUS_FRAMECNT_MASK) >>
+ FIMC_REG_CISTATUS_FRAMECNT_SHIFT;
+}
+
+/* Return an index to the buffer being written previously. */
+s32 fimc_hw_get_prev_frame_index(struct fimc_dev *dev)
+{
+ s32 reg;
+
+ if (!dev->drv_data->cistatus2)
+ return -1;
+
+ reg = readl(dev->regs + FIMC_REG_CISTATUS2);
+ return ((reg >> 7) & 0x3f) - 1;
+}
+
+/* Locking: the caller holds fimc->slock */
+void fimc_activate_capture(struct fimc_ctx *ctx)
+{
+ fimc_hw_enable_scaler(ctx->fimc_dev, ctx->scaler.enabled);
+ fimc_hw_enable_capture(ctx);
+}
+
+void fimc_deactivate_capture(struct fimc_dev *fimc)
+{
+ fimc_hw_en_lastirq(fimc, true);
+ fimc_hw_disable_capture(fimc);
+ fimc_hw_enable_scaler(fimc, false);
+ fimc_hw_en_lastirq(fimc, false);
+}
+
+int fimc_hw_camblk_cfg_writeback(struct fimc_dev *fimc)
+{
+ struct regmap *map = fimc->sysreg;
+ unsigned int mask, val, camblk_cfg;
+ int ret;
+
+ if (map == NULL)
+ return 0;
+
+ ret = regmap_read(map, SYSREG_CAMBLK, &camblk_cfg);
+ if (ret < 0 || ((camblk_cfg & 0x00700000) >> 20 != 0x3))
+ return ret;
+
+ if (!WARN(fimc->id >= 3, "not supported id: %d\n", fimc->id))
+ val = 0x1 << (fimc->id + 20);
+ else
+ val = 0;
+
+ mask = SYSREG_CAMBLK_FIFORST_ISP | SYSREG_CAMBLK_ISPWB_FULL_EN;
+ ret = regmap_update_bits(map, SYSREG_CAMBLK, mask, val);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ val |= SYSREG_CAMBLK_FIFORST_ISP;
+ ret = regmap_update_bits(map, SYSREG_CAMBLK, mask, val);
+ if (ret < 0)
+ return ret;
+
+ mask = SYSREG_ISPBLK_FIFORST_CAM_BLK;
+ ret = regmap_update_bits(map, SYSREG_ISPBLK, mask, ~mask);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ return regmap_update_bits(map, SYSREG_ISPBLK, mask, mask);
+}
diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-reg.h b/drivers/media/platform/samsung/exynos4-is/fimc-reg.h
new file mode 100644
index 0000000000..b9b33aa1f1
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/fimc-reg.h
@@ -0,0 +1,338 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung camera host interface (FIMC) registers definition
+ *
+ * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef FIMC_REG_H_
+#define FIMC_REG_H_
+
+#include <linux/bitops.h>
+
+#include "fimc-core.h"
+
+/* Input source format */
+#define FIMC_REG_CISRCFMT 0x00
+#define FIMC_REG_CISRCFMT_ITU601_8BIT BIT(31)
+#define FIMC_REG_CISRCFMT_ITU601_16BIT BIT(29)
+#define FIMC_REG_CISRCFMT_ORDER422_YCBYCR (0 << 14)
+#define FIMC_REG_CISRCFMT_ORDER422_YCRYCB (1 << 14)
+#define FIMC_REG_CISRCFMT_ORDER422_CBYCRY (2 << 14)
+#define FIMC_REG_CISRCFMT_ORDER422_CRYCBY (3 << 14)
+
+/* Window offset */
+#define FIMC_REG_CIWDOFST 0x04
+#define FIMC_REG_CIWDOFST_OFF_EN BIT(31)
+#define FIMC_REG_CIWDOFST_CLROVFIY BIT(30)
+#define FIMC_REG_CIWDOFST_CLROVRLB BIT(29)
+#define FIMC_REG_CIWDOFST_HOROFF_MASK (0x7ff << 16)
+#define FIMC_REG_CIWDOFST_CLROVFICB BIT(15)
+#define FIMC_REG_CIWDOFST_CLROVFICR BIT(14)
+#define FIMC_REG_CIWDOFST_VEROFF_MASK (0xfff << 0)
+
+/* Global control */
+#define FIMC_REG_CIGCTRL 0x08
+#define FIMC_REG_CIGCTRL_SWRST BIT(31)
+#define FIMC_REG_CIGCTRL_CAMRST_A BIT(30)
+#define FIMC_REG_CIGCTRL_SELCAM_ITU_A BIT(29)
+#define FIMC_REG_CIGCTRL_TESTPAT_NORMAL (0 << 27)
+#define FIMC_REG_CIGCTRL_TESTPAT_COLOR_BAR (1 << 27)
+#define FIMC_REG_CIGCTRL_TESTPAT_HOR_INC (2 << 27)
+#define FIMC_REG_CIGCTRL_TESTPAT_VER_INC (3 << 27)
+#define FIMC_REG_CIGCTRL_TESTPAT_MASK (3 << 27)
+#define FIMC_REG_CIGCTRL_TESTPAT_SHIFT 27
+#define FIMC_REG_CIGCTRL_INVPOLPCLK BIT(26)
+#define FIMC_REG_CIGCTRL_INVPOLVSYNC BIT(25)
+#define FIMC_REG_CIGCTRL_INVPOLHREF BIT(24)
+#define FIMC_REG_CIGCTRL_IRQ_OVFEN BIT(22)
+#define FIMC_REG_CIGCTRL_HREF_MASK BIT(21)
+#define FIMC_REG_CIGCTRL_IRQ_LEVEL BIT(20)
+#define FIMC_REG_CIGCTRL_IRQ_CLR BIT(19)
+#define FIMC_REG_CIGCTRL_IRQ_ENABLE BIT(16)
+#define FIMC_REG_CIGCTRL_SHDW_DISABLE BIT(12)
+/* 0 - selects Writeback A (LCD), 1 - selects Writeback B (LCD/ISP) */
+#define FIMC_REG_CIGCTRL_SELWB_A BIT(10)
+#define FIMC_REG_CIGCTRL_CAM_JPEG BIT(8)
+#define FIMC_REG_CIGCTRL_SELCAM_MIPI_A BIT(7)
+#define FIMC_REG_CIGCTRL_CAMIF_SELWB BIT(6)
+/* 0 - ITU601; 1 - ITU709 */
+#define FIMC_REG_CIGCTRL_CSC_ITU601_709 BIT(5)
+#define FIMC_REG_CIGCTRL_INVPOLHSYNC BIT(4)
+#define FIMC_REG_CIGCTRL_SELCAM_MIPI BIT(3)
+#define FIMC_REG_CIGCTRL_INVPOLFIELD BIT(1)
+#define FIMC_REG_CIGCTRL_INTERLACE BIT(0)
+
+/* Window offset 2 */
+#define FIMC_REG_CIWDOFST2 0x14
+#define FIMC_REG_CIWDOFST2_HOROFF_MASK (0xfff << 16)
+#define FIMC_REG_CIWDOFST2_VEROFF_MASK (0xfff << 0)
+
+/* Output DMA Y/Cb/Cr plane start addresses */
+#define FIMC_REG_CIOYSA(n) (0x18 + (n) * 4)
+#define FIMC_REG_CIOCBSA(n) (0x28 + (n) * 4)
+#define FIMC_REG_CIOCRSA(n) (0x38 + (n) * 4)
+
+/* Target image format */
+#define FIMC_REG_CITRGFMT 0x48
+#define FIMC_REG_CITRGFMT_INROT90 BIT(31)
+#define FIMC_REG_CITRGFMT_YCBCR420 (0 << 29)
+#define FIMC_REG_CITRGFMT_YCBCR422 (1 << 29)
+#define FIMC_REG_CITRGFMT_YCBCR422_1P (2 << 29)
+#define FIMC_REG_CITRGFMT_RGB (3 << 29)
+#define FIMC_REG_CITRGFMT_FMT_MASK (3 << 29)
+#define FIMC_REG_CITRGFMT_HSIZE_MASK (0xfff << 16)
+#define FIMC_REG_CITRGFMT_FLIP_SHIFT 14
+#define FIMC_REG_CITRGFMT_FLIP_NORMAL (0 << 14)
+#define FIMC_REG_CITRGFMT_FLIP_X_MIRROR (1 << 14)
+#define FIMC_REG_CITRGFMT_FLIP_Y_MIRROR (2 << 14)
+#define FIMC_REG_CITRGFMT_FLIP_180 (3 << 14)
+#define FIMC_REG_CITRGFMT_FLIP_MASK (3 << 14)
+#define FIMC_REG_CITRGFMT_OUTROT90 BIT(13)
+#define FIMC_REG_CITRGFMT_VSIZE_MASK (0xfff << 0)
+
+/* Output DMA control */
+#define FIMC_REG_CIOCTRL 0x4c
+#define FIMC_REG_CIOCTRL_ORDER422_MASK (3 << 0)
+#define FIMC_REG_CIOCTRL_ORDER422_YCBYCR (0 << 0)
+#define FIMC_REG_CIOCTRL_ORDER422_YCRYCB (1 << 0)
+#define FIMC_REG_CIOCTRL_ORDER422_CBYCRY (2 << 0)
+#define FIMC_REG_CIOCTRL_ORDER422_CRYCBY (3 << 0)
+#define FIMC_REG_CIOCTRL_LASTIRQ_ENABLE BIT(2)
+#define FIMC_REG_CIOCTRL_YCBCR_3PLANE (0 << 3)
+#define FIMC_REG_CIOCTRL_YCBCR_2PLANE (1 << 3)
+#define FIMC_REG_CIOCTRL_YCBCR_PLANE_MASK (1 << 3)
+#define FIMC_REG_CIOCTRL_ALPHA_OUT_MASK (0xff << 4)
+#define FIMC_REG_CIOCTRL_RGB16FMT_MASK (3 << 16)
+#define FIMC_REG_CIOCTRL_RGB565 (0 << 16)
+#define FIMC_REG_CIOCTRL_ARGB1555 (1 << 16)
+#define FIMC_REG_CIOCTRL_ARGB4444 (2 << 16)
+#define FIMC_REG_CIOCTRL_ORDER2P_SHIFT 24
+#define FIMC_REG_CIOCTRL_ORDER2P_MASK (3 << 24)
+#define FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB (0 << 24)
+
+/* Pre-scaler control 1 */
+#define FIMC_REG_CISCPRERATIO 0x50
+
+#define FIMC_REG_CISCPREDST 0x54
+
+/* Main scaler control */
+#define FIMC_REG_CISCCTRL 0x58
+#define FIMC_REG_CISCCTRL_SCALERBYPASS BIT(31)
+#define FIMC_REG_CISCCTRL_SCALEUP_H BIT(30)
+#define FIMC_REG_CISCCTRL_SCALEUP_V BIT(29)
+#define FIMC_REG_CISCCTRL_CSCR2Y_WIDE BIT(28)
+#define FIMC_REG_CISCCTRL_CSCY2R_WIDE BIT(27)
+#define FIMC_REG_CISCCTRL_LCDPATHEN_FIFO BIT(26)
+#define FIMC_REG_CISCCTRL_INTERLACE BIT(25)
+#define FIMC_REG_CISCCTRL_SCALERSTART BIT(15)
+#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB565 (0 << 13)
+#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB666 (1 << 13)
+#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB888 (2 << 13)
+#define FIMC_REG_CISCCTRL_INRGB_FMT_MASK (3 << 13)
+#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11)
+#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11)
+#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11)
+#define FIMC_REG_CISCCTRL_OUTRGB_FMT_MASK (3 << 11)
+#define FIMC_REG_CISCCTRL_RGB_EXT BIT(10)
+#define FIMC_REG_CISCCTRL_ONE2ONE BIT(9)
+#define FIMC_REG_CISCCTRL_MHRATIO(x) ((x) << 16)
+#define FIMC_REG_CISCCTRL_MVRATIO(x) ((x) << 0)
+#define FIMC_REG_CISCCTRL_MHRATIO_MASK (0x1ff << 16)
+#define FIMC_REG_CISCCTRL_MVRATIO_MASK (0x1ff << 0)
+#define FIMC_REG_CISCCTRL_MHRATIO_EXT(x) (((x) >> 6) << 16)
+#define FIMC_REG_CISCCTRL_MVRATIO_EXT(x) (((x) >> 6) << 0)
+
+/* Target area */
+#define FIMC_REG_CITAREA 0x5c
+#define FIMC_REG_CITAREA_MASK 0x0fffffff
+
+/* General status */
+#define FIMC_REG_CISTATUS 0x64
+#define FIMC_REG_CISTATUS_OVFIY BIT(31)
+#define FIMC_REG_CISTATUS_OVFICB BIT(30)
+#define FIMC_REG_CISTATUS_OVFICR BIT(29)
+#define FIMC_REG_CISTATUS_VSYNC BIT(28)
+#define FIMC_REG_CISTATUS_FRAMECNT_MASK (3 << 26)
+#define FIMC_REG_CISTATUS_FRAMECNT_SHIFT 26
+#define FIMC_REG_CISTATUS_WINOFF_EN BIT(25)
+#define FIMC_REG_CISTATUS_IMGCPT_EN BIT(22)
+#define FIMC_REG_CISTATUS_IMGCPT_SCEN BIT(21)
+#define FIMC_REG_CISTATUS_VSYNC_A BIT(20)
+#define FIMC_REG_CISTATUS_VSYNC_B BIT(19)
+#define FIMC_REG_CISTATUS_OVRLB BIT(18)
+#define FIMC_REG_CISTATUS_FRAME_END BIT(17)
+#define FIMC_REG_CISTATUS_LASTCAPT_END BIT(16)
+#define FIMC_REG_CISTATUS_VVALID_A BIT(15)
+#define FIMC_REG_CISTATUS_VVALID_B BIT(14)
+
+/* Indexes to the last and the currently processed buffer. */
+#define FIMC_REG_CISTATUS2 0x68
+
+/* Image capture control */
+#define FIMC_REG_CIIMGCPT 0xc0
+#define FIMC_REG_CIIMGCPT_IMGCPTEN BIT(31)
+#define FIMC_REG_CIIMGCPT_IMGCPTEN_SC BIT(30)
+#define FIMC_REG_CIIMGCPT_CPT_FREN_ENABLE BIT(25)
+#define FIMC_REG_CIIMGCPT_CPT_FRMOD_CNT BIT(18)
+
+/* Frame capture sequence */
+#define FIMC_REG_CICPTSEQ 0xc4
+
+/* Image effect */
+#define FIMC_REG_CIIMGEFF 0xd0
+#define FIMC_REG_CIIMGEFF_IE_ENABLE BIT(30)
+#define FIMC_REG_CIIMGEFF_IE_SC_BEFORE (0 << 29)
+#define FIMC_REG_CIIMGEFF_IE_SC_AFTER (1 << 29)
+#define FIMC_REG_CIIMGEFF_FIN_BYPASS (0 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_ARBITRARY (1 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_NEGATIVE (2 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_ARTFREEZE (3 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_EMBOSSING (4 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_SILHOUETTE (5 << 26)
+#define FIMC_REG_CIIMGEFF_FIN_MASK (7 << 26)
+#define FIMC_REG_CIIMGEFF_PAT_CBCR_MASK ((0xff << 13) | 0xff)
+
+/* Input DMA Y/Cb/Cr plane start address 0/1 */
+#define FIMC_REG_CIIYSA(n) (0xd4 + (n) * 0x70)
+#define FIMC_REG_CIICBSA(n) (0xd8 + (n) * 0x70)
+#define FIMC_REG_CIICRSA(n) (0xdc + (n) * 0x70)
+
+/* Real input DMA image size */
+#define FIMC_REG_CIREAL_ISIZE 0xf8
+#define FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN BIT(31)
+#define FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS BIT(30)
+
+/* Input DMA control */
+#define FIMC_REG_MSCTRL 0xfc
+#define FIMC_REG_MSCTRL_IN_BURST_COUNT_MASK (0xf << 24)
+#define FIMC_REG_MSCTRL_2P_IN_ORDER_MASK (3 << 16)
+#define FIMC_REG_MSCTRL_2P_IN_ORDER_SHIFT 16
+#define FIMC_REG_MSCTRL_C_INT_IN_3PLANE (0 << 15)
+#define FIMC_REG_MSCTRL_C_INT_IN_2PLANE (1 << 15)
+#define FIMC_REG_MSCTRL_C_INT_IN_MASK (1 << 15)
+#define FIMC_REG_MSCTRL_FLIP_SHIFT 13
+#define FIMC_REG_MSCTRL_FLIP_MASK (3 << 13)
+#define FIMC_REG_MSCTRL_FLIP_NORMAL (0 << 13)
+#define FIMC_REG_MSCTRL_FLIP_X_MIRROR (1 << 13)
+#define FIMC_REG_MSCTRL_FLIP_Y_MIRROR (2 << 13)
+#define FIMC_REG_MSCTRL_FLIP_180 (3 << 13)
+#define FIMC_REG_MSCTRL_FIFO_CTRL_FULL BIT(12)
+#define FIMC_REG_MSCTRL_ORDER422_SHIFT 4
+#define FIMC_REG_MSCTRL_ORDER422_CRYCBY (0 << 4)
+#define FIMC_REG_MSCTRL_ORDER422_YCRYCB (1 << 4)
+#define FIMC_REG_MSCTRL_ORDER422_CBYCRY (2 << 4)
+#define FIMC_REG_MSCTRL_ORDER422_YCBYCR (3 << 4)
+#define FIMC_REG_MSCTRL_ORDER422_MASK (3 << 4)
+#define FIMC_REG_MSCTRL_INPUT_EXTCAM (0 << 3)
+#define FIMC_REG_MSCTRL_INPUT_MEMORY BIT(3)
+#define FIMC_REG_MSCTRL_INPUT_MASK BIT(3)
+#define FIMC_REG_MSCTRL_INFORMAT_YCBCR420 (0 << 1)
+#define FIMC_REG_MSCTRL_INFORMAT_YCBCR422 (1 << 1)
+#define FIMC_REG_MSCTRL_INFORMAT_YCBCR422_1P (2 << 1)
+#define FIMC_REG_MSCTRL_INFORMAT_RGB (3 << 1)
+#define FIMC_REG_MSCTRL_INFORMAT_MASK (3 << 1)
+#define FIMC_REG_MSCTRL_ENVID BIT(0)
+#define FIMC_REG_MSCTRL_IN_BURST_COUNT(x) ((x) << 24)
+
+/* Output DMA Y/Cb/Cr offset */
+#define FIMC_REG_CIOYOFF 0x168
+#define FIMC_REG_CIOCBOFF 0x16c
+#define FIMC_REG_CIOCROFF 0x170
+
+/* Input DMA Y/Cb/Cr offset */
+#define FIMC_REG_CIIYOFF 0x174
+#define FIMC_REG_CIICBOFF 0x178
+#define FIMC_REG_CIICROFF 0x17c
+
+/* Input DMA original image size */
+#define FIMC_REG_ORGISIZE 0x180
+
+/* Output DMA original image size */
+#define FIMC_REG_ORGOSIZE 0x184
+
+/* Real output DMA image size (extension register) */
+#define FIMC_REG_CIEXTEN 0x188
+#define FIMC_REG_CIEXTEN_MHRATIO_EXT(x) (((x) & 0x3f) << 10)
+#define FIMC_REG_CIEXTEN_MVRATIO_EXT(x) ((x) & 0x3f)
+#define FIMC_REG_CIEXTEN_MHRATIO_EXT_MASK (0x3f << 10)
+#define FIMC_REG_CIEXTEN_MVRATIO_EXT_MASK 0x3f
+
+#define FIMC_REG_CIDMAPARAM 0x18c
+#define FIMC_REG_CIDMAPARAM_R_LINEAR (0 << 29)
+#define FIMC_REG_CIDMAPARAM_R_64X32 (3 << 29)
+#define FIMC_REG_CIDMAPARAM_W_LINEAR (0 << 13)
+#define FIMC_REG_CIDMAPARAM_W_64X32 (3 << 13)
+#define FIMC_REG_CIDMAPARAM_TILE_MASK ((3 << 29) | (3 << 13))
+
+/* MIPI CSI image format */
+#define FIMC_REG_CSIIMGFMT 0x194
+#define FIMC_REG_CSIIMGFMT_YCBCR422_8BIT 0x1e
+#define FIMC_REG_CSIIMGFMT_RAW8 0x2a
+#define FIMC_REG_CSIIMGFMT_RAW10 0x2b
+#define FIMC_REG_CSIIMGFMT_RAW12 0x2c
+/* User defined formats. x = 0...16. */
+#define FIMC_REG_CSIIMGFMT_USER(x) (0x30 + x - 1)
+
+/* Output frame buffer sequence mask */
+#define FIMC_REG_CIFCNTSEQ 0x1fc
+
+/* SYSREG ISP Writeback register address offsets */
+#define SYSREG_ISPBLK 0x020c
+#define SYSREG_ISPBLK_FIFORST_CAM_BLK BIT(7)
+
+#define SYSREG_CAMBLK 0x0218
+#define SYSREG_CAMBLK_FIFORST_ISP BIT(15)
+#define SYSREG_CAMBLK_ISPWB_FULL_EN (7 << 20)
+
+/*
+ * Function declarations
+ */
+void fimc_hw_reset(struct fimc_dev *fimc);
+void fimc_hw_set_rotation(struct fimc_ctx *ctx);
+void fimc_hw_set_target_format(struct fimc_ctx *ctx);
+void fimc_hw_set_out_dma(struct fimc_ctx *ctx);
+void fimc_hw_en_lastirq(struct fimc_dev *fimc, int enable);
+void fimc_hw_en_irq(struct fimc_dev *fimc, int enable);
+void fimc_hw_set_prescaler(struct fimc_ctx *ctx);
+void fimc_hw_set_mainscaler(struct fimc_ctx *ctx);
+void fimc_hw_enable_capture(struct fimc_ctx *ctx);
+void fimc_hw_set_effect(struct fimc_ctx *ctx);
+void fimc_hw_set_rgb_alpha(struct fimc_ctx *ctx);
+void fimc_hw_set_in_dma(struct fimc_ctx *ctx);
+void fimc_hw_set_input_path(struct fimc_ctx *ctx);
+void fimc_hw_set_output_path(struct fimc_ctx *ctx);
+void fimc_hw_set_input_addr(struct fimc_dev *fimc, struct fimc_addr *addr);
+void fimc_hw_set_output_addr(struct fimc_dev *fimc, struct fimc_addr *addr,
+ int index);
+int fimc_hw_set_camera_source(struct fimc_dev *fimc,
+ struct fimc_source_info *cam);
+void fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f);
+int fimc_hw_set_camera_polarity(struct fimc_dev *fimc,
+ struct fimc_source_info *cam);
+int fimc_hw_set_camera_type(struct fimc_dev *fimc,
+ struct fimc_source_info *cam);
+void fimc_hw_clear_irq(struct fimc_dev *dev);
+void fimc_hw_enable_scaler(struct fimc_dev *dev, bool on);
+void fimc_hw_activate_input_dma(struct fimc_dev *dev, bool on);
+void fimc_hw_disable_capture(struct fimc_dev *dev);
+s32 fimc_hw_get_frame_index(struct fimc_dev *dev);
+s32 fimc_hw_get_prev_frame_index(struct fimc_dev *dev);
+int fimc_hw_camblk_cfg_writeback(struct fimc_dev *fimc);
+void fimc_activate_capture(struct fimc_ctx *ctx);
+void fimc_deactivate_capture(struct fimc_dev *fimc);
+
+/**
+ * fimc_hw_set_dma_seq - configure output DMA buffer sequence
+ * @dev: fimc device
+ * @mask: bitmask for the DMA output buffer registers, set to 0 to skip buffer
+ * This function masks output DMA ring buffers, it allows to select which of
+ * the 32 available output buffer address registers will be used by the DMA
+ * engine.
+ */
+static inline void fimc_hw_set_dma_seq(struct fimc_dev *dev, u32 mask)
+{
+ writel(mask, dev->regs + FIMC_REG_CIFCNTSEQ);
+}
+
+#endif /* FIMC_REG_H_ */
diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c
new file mode 100644
index 0000000000..5f10bb4eb4
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c
@@ -0,0 +1,1602 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * S5P/EXYNOS4 SoC series camera host interface media device driver
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_graph.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fwnode.h>
+#include <media/media-device.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#include "media-dev.h"
+#include "fimc-core.h"
+#include "fimc-is.h"
+#include "fimc-lite.h"
+#include "mipi-csis.h"
+
+/* Set up image sensor subdev -> FIMC capture node notifications. */
+static void __setup_sensor_notification(struct fimc_md *fmd,
+ struct v4l2_subdev *sensor,
+ struct v4l2_subdev *fimc_sd)
+{
+ struct fimc_source_info *src_inf;
+ struct fimc_sensor_info *md_si;
+ unsigned long flags;
+
+ src_inf = v4l2_get_subdev_hostdata(sensor);
+ if (!src_inf || WARN_ON(fmd == NULL))
+ return;
+
+ md_si = source_to_sensor_info(src_inf);
+ spin_lock_irqsave(&fmd->slock, flags);
+ md_si->host = v4l2_get_subdevdata(fimc_sd);
+ spin_unlock_irqrestore(&fmd->slock, flags);
+}
+
+/**
+ * fimc_pipeline_prepare - update pipeline information with subdevice pointers
+ * @p: fimc pipeline
+ * @me: media entity terminating the pipeline
+ *
+ * Caller holds the graph mutex.
+ */
+static void fimc_pipeline_prepare(struct fimc_pipeline *p,
+ struct media_entity *me)
+{
+ struct fimc_md *fmd = entity_to_fimc_mdev(me);
+ struct v4l2_subdev *sd;
+ struct v4l2_subdev *sensor = NULL;
+ int i;
+
+ for (i = 0; i < IDX_MAX; i++)
+ p->subdevs[i] = NULL;
+
+ while (1) {
+ struct media_pad *pad = NULL;
+
+ /* Find remote source pad */
+ for (i = 0; i < me->num_pads; i++) {
+ struct media_pad *spad = &me->pads[i];
+ if (!(spad->flags & MEDIA_PAD_FL_SINK))
+ continue;
+ pad = media_pad_remote_pad_first(spad);
+ if (pad)
+ break;
+ }
+
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+ sd = media_entity_to_v4l2_subdev(pad->entity);
+
+ switch (sd->grp_id) {
+ case GRP_ID_SENSOR:
+ sensor = sd;
+ fallthrough;
+ case GRP_ID_FIMC_IS_SENSOR:
+ p->subdevs[IDX_SENSOR] = sd;
+ break;
+ case GRP_ID_CSIS:
+ p->subdevs[IDX_CSIS] = sd;
+ break;
+ case GRP_ID_FLITE:
+ p->subdevs[IDX_FLITE] = sd;
+ break;
+ case GRP_ID_FIMC:
+ p->subdevs[IDX_FIMC] = sd;
+ break;
+ case GRP_ID_FIMC_IS:
+ p->subdevs[IDX_IS_ISP] = sd;
+ break;
+ default:
+ break;
+ }
+ me = &sd->entity;
+ if (me->num_pads == 1)
+ break;
+ }
+
+ if (sensor && p->subdevs[IDX_FIMC])
+ __setup_sensor_notification(fmd, sensor, p->subdevs[IDX_FIMC]);
+}
+
+/**
+ * __subdev_set_power - change power state of a single subdev
+ * @sd: subdevice to change power state for
+ * @on: 1 to enable power or 0 to disable
+ *
+ * Return result of s_power subdev operation or -ENXIO if sd argument
+ * is NULL. Return 0 if the subdevice does not implement s_power.
+ */
+static int __subdev_set_power(struct v4l2_subdev *sd, int on)
+{
+ int *use_count;
+ int ret;
+
+ if (sd == NULL)
+ return -ENXIO;
+
+ use_count = &sd->entity.use_count;
+ if (on && (*use_count)++ > 0)
+ return 0;
+ else if (!on && (*use_count == 0 || --(*use_count) > 0))
+ return 0;
+ ret = v4l2_subdev_call(sd, core, s_power, on);
+
+ return ret != -ENOIOCTLCMD ? ret : 0;
+}
+
+/**
+ * fimc_pipeline_s_power - change power state of all pipeline subdevs
+ * @p: fimc device terminating the pipeline
+ * @on: true to power on, false to power off
+ *
+ * Needs to be called with the graph mutex held.
+ */
+static int fimc_pipeline_s_power(struct fimc_pipeline *p, bool on)
+{
+ static const u8 seq[2][IDX_MAX - 1] = {
+ { IDX_IS_ISP, IDX_SENSOR, IDX_CSIS, IDX_FLITE },
+ { IDX_CSIS, IDX_FLITE, IDX_SENSOR, IDX_IS_ISP },
+ };
+ int i, ret = 0;
+
+ if (p->subdevs[IDX_SENSOR] == NULL)
+ return -ENXIO;
+
+ for (i = 0; i < IDX_MAX - 1; i++) {
+ unsigned int idx = seq[on][i];
+
+ ret = __subdev_set_power(p->subdevs[idx], on);
+
+
+ if (ret < 0 && ret != -ENXIO)
+ goto error;
+ }
+ return 0;
+error:
+ for (; i >= 0; i--) {
+ unsigned int idx = seq[on][i];
+ __subdev_set_power(p->subdevs[idx], !on);
+ }
+ return ret;
+}
+
+/**
+ * __fimc_pipeline_enable - enable power of all pipeline subdevs
+ * and the sensor clock
+ * @ep: video pipeline structure
+ * @fmd: fimc media device
+ *
+ * Called with the graph mutex held.
+ */
+static int __fimc_pipeline_enable(struct exynos_media_pipeline *ep,
+ struct fimc_md *fmd)
+{
+ struct fimc_pipeline *p = to_fimc_pipeline(ep);
+ int ret;
+
+ /* Enable PXLASYNC clock if this pipeline includes FIMC-IS */
+ if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) {
+ ret = clk_prepare_enable(fmd->wbclk[CLK_IDX_WB_B]);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = fimc_pipeline_s_power(p, 1);
+ if (!ret)
+ return 0;
+
+ if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP])
+ clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]);
+
+ return ret;
+}
+
+/**
+ * __fimc_pipeline_open - update the pipeline information, enable power
+ * of all pipeline subdevs and the sensor clock
+ * @ep: fimc device terminating the pipeline
+ * @me: media entity to start graph walk with
+ * @prepare: true to walk the current pipeline and acquire all subdevs
+ *
+ * Called with the graph mutex held.
+ */
+static int __fimc_pipeline_open(struct exynos_media_pipeline *ep,
+ struct media_entity *me, bool prepare)
+{
+ struct fimc_md *fmd = entity_to_fimc_mdev(me);
+ struct fimc_pipeline *p = to_fimc_pipeline(ep);
+ struct v4l2_subdev *sd;
+
+ if (WARN_ON(p == NULL || me == NULL))
+ return -EINVAL;
+
+ if (prepare)
+ fimc_pipeline_prepare(p, me);
+
+ sd = p->subdevs[IDX_SENSOR];
+ if (sd == NULL) {
+ pr_warn("%s(): No sensor subdev\n", __func__);
+ /*
+ * Pipeline open cannot fail so as to make it possible
+ * for the user space to configure the pipeline.
+ */
+ return 0;
+ }
+
+ return __fimc_pipeline_enable(ep, fmd);
+}
+
+/**
+ * __fimc_pipeline_close - disable the sensor clock and pipeline power
+ * @ep: fimc device terminating the pipeline
+ *
+ * Disable power of all subdevs and turn the external sensor clock off.
+ */
+static int __fimc_pipeline_close(struct exynos_media_pipeline *ep)
+{
+ struct fimc_pipeline *p = to_fimc_pipeline(ep);
+ struct v4l2_subdev *sd = p ? p->subdevs[IDX_SENSOR] : NULL;
+ struct fimc_md *fmd;
+ int ret;
+
+ if (sd == NULL) {
+ pr_warn("%s(): No sensor subdev\n", __func__);
+ return 0;
+ }
+
+ ret = fimc_pipeline_s_power(p, 0);
+
+ fmd = entity_to_fimc_mdev(&sd->entity);
+
+ /* Disable PXLASYNC clock if this pipeline includes FIMC-IS */
+ if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP])
+ clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]);
+
+ return ret == -ENXIO ? 0 : ret;
+}
+
+/**
+ * __fimc_pipeline_s_stream - call s_stream() on pipeline subdevs
+ * @ep: video pipeline structure
+ * @on: passed as the s_stream() callback argument
+ */
+static int __fimc_pipeline_s_stream(struct exynos_media_pipeline *ep, bool on)
+{
+ static const u8 seq[2][IDX_MAX] = {
+ { IDX_FIMC, IDX_SENSOR, IDX_IS_ISP, IDX_CSIS, IDX_FLITE },
+ { IDX_CSIS, IDX_FLITE, IDX_FIMC, IDX_SENSOR, IDX_IS_ISP },
+ };
+ struct fimc_pipeline *p = to_fimc_pipeline(ep);
+ enum fimc_subdev_index sd_id;
+ int i, ret = 0;
+
+ if (p->subdevs[IDX_SENSOR] == NULL) {
+ struct fimc_md *fmd;
+ struct v4l2_subdev *sd = p->subdevs[IDX_CSIS];
+
+ if (!sd)
+ sd = p->subdevs[IDX_FIMC];
+
+ if (!sd) {
+ /*
+ * If neither CSIS nor FIMC was set up,
+ * it's impossible to have any sensors
+ */
+ return -ENODEV;
+ }
+
+ fmd = entity_to_fimc_mdev(&sd->entity);
+
+ if (!fmd->user_subdev_api) {
+ /*
+ * Sensor must be already discovered if we
+ * aren't in the user_subdev_api mode
+ */
+ return -ENODEV;
+ }
+
+ /* Get pipeline sink entity */
+ if (p->subdevs[IDX_FIMC])
+ sd_id = IDX_FIMC;
+ else if (p->subdevs[IDX_IS_ISP])
+ sd_id = IDX_IS_ISP;
+ else if (p->subdevs[IDX_FLITE])
+ sd_id = IDX_FLITE;
+ else
+ return -ENODEV;
+
+ /*
+ * Sensor could have been linked between open and STREAMON -
+ * check if this is the case.
+ */
+ fimc_pipeline_prepare(p, &p->subdevs[sd_id]->entity);
+
+ if (p->subdevs[IDX_SENSOR] == NULL)
+ return -ENODEV;
+
+ ret = __fimc_pipeline_enable(ep, fmd);
+ if (ret < 0)
+ return ret;
+
+ }
+
+ for (i = 0; i < IDX_MAX; i++) {
+ unsigned int idx = seq[on][i];
+
+ ret = v4l2_subdev_call(p->subdevs[idx], video, s_stream, on);
+
+ if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+ goto error;
+ }
+
+ return 0;
+error:
+ fimc_pipeline_s_power(p, !on);
+ for (; i >= 0; i--) {
+ unsigned int idx = seq[on][i];
+ v4l2_subdev_call(p->subdevs[idx], video, s_stream, !on);
+ }
+ return ret;
+}
+
+/* Media pipeline operations for the FIMC/FIMC-LITE video device driver */
+static const struct exynos_media_pipeline_ops fimc_pipeline_ops = {
+ .open = __fimc_pipeline_open,
+ .close = __fimc_pipeline_close,
+ .set_stream = __fimc_pipeline_s_stream,
+};
+
+static struct exynos_media_pipeline *fimc_md_pipeline_create(
+ struct fimc_md *fmd)
+{
+ struct fimc_pipeline *p;
+
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return NULL;
+
+ list_add_tail(&p->list, &fmd->pipelines);
+
+ p->ep.ops = &fimc_pipeline_ops;
+ return &p->ep;
+}
+
+static void fimc_md_pipelines_free(struct fimc_md *fmd)
+{
+ while (!list_empty(&fmd->pipelines)) {
+ struct fimc_pipeline *p;
+
+ p = list_entry(fmd->pipelines.next, typeof(*p), list);
+ list_del(&p->list);
+ kfree(p);
+ }
+}
+
+static int fimc_md_parse_one_endpoint(struct fimc_md *fmd,
+ struct device_node *ep)
+{
+ int index = fmd->num_sensors;
+ struct fimc_source_info *pd = &fmd->sensor[index].pdata;
+ struct device_node *rem, *np;
+ struct v4l2_async_connection *asd;
+ struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 };
+ int ret;
+
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint);
+ if (ret) {
+ of_node_put(ep);
+ return ret;
+ }
+
+ if (WARN_ON(endpoint.base.port == 0) || index >= FIMC_MAX_SENSORS) {
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ pd->mux_id = (endpoint.base.port - 1) & 0x1;
+
+ rem = of_graph_get_remote_port_parent(ep);
+ if (rem == NULL) {
+ v4l2_info(&fmd->v4l2_dev, "Remote device at %pOF not found\n",
+ ep);
+ of_node_put(ep);
+ return 0;
+ }
+
+ if (fimc_input_is_parallel(endpoint.base.port)) {
+ if (endpoint.bus_type == V4L2_MBUS_PARALLEL)
+ pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601;
+ else
+ pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656;
+ pd->flags = endpoint.bus.parallel.flags;
+ } else if (fimc_input_is_mipi_csi(endpoint.base.port)) {
+ /*
+ * MIPI CSI-2: only input mux selection and
+ * the sensor's clock frequency is needed.
+ */
+ pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2;
+ } else {
+ v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %pOF\n",
+ endpoint.base.port, rem);
+ }
+ /*
+ * For FIMC-IS handled sensors, that are placed under i2c-isp device
+ * node, FIMC is connected to the FIMC-IS through its ISP Writeback
+ * input. Sensors are attached to the FIMC-LITE hostdata interface
+ * directly or through MIPI-CSIS, depending on the external media bus
+ * used. This needs to be handled in a more reliable way, not by just
+ * checking parent's node name.
+ */
+ np = of_get_parent(rem);
+ of_node_put(rem);
+
+ if (of_node_name_eq(np, "i2c-isp"))
+ pd->fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK;
+ else
+ pd->fimc_bus_type = pd->sensor_bus_type;
+ of_node_put(np);
+
+ if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor))) {
+ of_node_put(ep);
+ return -EINVAL;
+ }
+
+ asd = v4l2_async_nf_add_fwnode_remote(&fmd->subdev_notifier,
+ of_fwnode_handle(ep),
+ struct v4l2_async_connection);
+
+ of_node_put(ep);
+
+ if (IS_ERR(asd))
+ return PTR_ERR(asd);
+
+ fmd->sensor[index].asd = asd;
+ fmd->num_sensors++;
+
+ return 0;
+}
+
+/* Parse port node and register as a sub-device any sensor specified there. */
+static int fimc_md_parse_port_node(struct fimc_md *fmd,
+ struct device_node *port)
+{
+ struct device_node *ep;
+ int ret;
+
+ for_each_child_of_node(port, ep) {
+ ret = fimc_md_parse_one_endpoint(fmd, ep);
+ if (ret < 0) {
+ of_node_put(ep);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* Register all SoC external sub-devices */
+static int fimc_md_register_sensor_entities(struct fimc_md *fmd)
+{
+ struct device_node *parent = fmd->pdev->dev.of_node;
+ struct device_node *ports = NULL;
+ struct device_node *node;
+ int ret;
+
+ /*
+ * Runtime resume one of the FIMC entities to make sure
+ * the sclk_cam clocks are not globally disabled.
+ */
+ if (!fmd->pmf)
+ return -ENXIO;
+
+ ret = pm_runtime_resume_and_get(fmd->pmf);
+ if (ret < 0)
+ return ret;
+
+ fmd->num_sensors = 0;
+
+ /* Attach sensors linked to MIPI CSI-2 receivers */
+ for_each_available_child_of_node(parent, node) {
+ struct device_node *port;
+
+ if (!of_node_name_eq(node, "csis"))
+ continue;
+ /* The csis node can have only port subnode. */
+ port = of_get_next_child(node, NULL);
+ if (!port)
+ continue;
+
+ ret = fimc_md_parse_port_node(fmd, port);
+ of_node_put(port);
+ if (ret < 0) {
+ of_node_put(node);
+ goto cleanup;
+ }
+ }
+
+ /* Attach sensors listed in the parallel-ports node */
+ ports = of_get_child_by_name(parent, "parallel-ports");
+ if (!ports)
+ goto rpm_put;
+
+ for_each_child_of_node(ports, node) {
+ ret = fimc_md_parse_port_node(fmd, node);
+ if (ret < 0) {
+ of_node_put(node);
+ goto cleanup;
+ }
+ }
+ of_node_put(ports);
+
+rpm_put:
+ pm_runtime_put(fmd->pmf);
+ return 0;
+
+cleanup:
+ of_node_put(ports);
+ v4l2_async_nf_cleanup(&fmd->subdev_notifier);
+ pm_runtime_put(fmd->pmf);
+ return ret;
+}
+
+static int __of_get_csis_id(struct device_node *np)
+{
+ u32 reg = 0;
+
+ np = of_get_child_by_name(np, "port");
+ if (!np)
+ return -EINVAL;
+ of_property_read_u32(np, "reg", &reg);
+ of_node_put(np);
+ return reg - FIMC_INPUT_MIPI_CSI2_0;
+}
+
+/*
+ * MIPI-CSIS, FIMC and FIMC-LITE platform devices registration.
+ */
+static int register_fimc_lite_entity(struct fimc_md *fmd,
+ struct fimc_lite *fimc_lite)
+{
+ struct v4l2_subdev *sd;
+ struct exynos_media_pipeline *ep;
+ int ret;
+
+ if (WARN_ON(fimc_lite->index >= FIMC_LITE_MAX_DEVS ||
+ fmd->fimc_lite[fimc_lite->index]))
+ return -EBUSY;
+
+ sd = &fimc_lite->subdev;
+ sd->grp_id = GRP_ID_FLITE;
+
+ ep = fimc_md_pipeline_create(fmd);
+ if (!ep)
+ return -ENOMEM;
+
+ v4l2_set_subdev_hostdata(sd, ep);
+
+ ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
+ if (!ret)
+ fmd->fimc_lite[fimc_lite->index] = fimc_lite;
+ else
+ v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.LITE%d\n",
+ fimc_lite->index);
+ return ret;
+}
+
+static int register_fimc_entity(struct fimc_md *fmd, struct fimc_dev *fimc)
+{
+ struct v4l2_subdev *sd;
+ struct exynos_media_pipeline *ep;
+ int ret;
+
+ if (WARN_ON(fimc->id >= FIMC_MAX_DEVS || fmd->fimc[fimc->id]))
+ return -EBUSY;
+
+ sd = &fimc->vid_cap.subdev;
+ sd->grp_id = GRP_ID_FIMC;
+
+ ep = fimc_md_pipeline_create(fmd);
+ if (!ep)
+ return -ENOMEM;
+
+ v4l2_set_subdev_hostdata(sd, ep);
+
+ ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
+ if (!ret) {
+ if (!fmd->pmf && fimc->pdev)
+ fmd->pmf = &fimc->pdev->dev;
+ fmd->fimc[fimc->id] = fimc;
+ fimc->vid_cap.user_subdev_api = fmd->user_subdev_api;
+ } else {
+ v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.%d (%d)\n",
+ fimc->id, ret);
+ }
+ return ret;
+}
+
+static int register_csis_entity(struct fimc_md *fmd,
+ struct platform_device *pdev,
+ struct v4l2_subdev *sd)
+{
+ struct device_node *node = pdev->dev.of_node;
+ int id, ret;
+
+ id = node ? __of_get_csis_id(node) : max(0, pdev->id);
+
+ if (WARN_ON(id < 0 || id >= CSIS_MAX_ENTITIES))
+ return -ENOENT;
+
+ if (WARN_ON(fmd->csis[id].sd))
+ return -EBUSY;
+
+ sd->grp_id = GRP_ID_CSIS;
+ ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
+ if (!ret)
+ fmd->csis[id].sd = sd;
+ else
+ v4l2_err(&fmd->v4l2_dev,
+ "Failed to register MIPI-CSIS.%d (%d)\n", id, ret);
+ return ret;
+}
+
+static int register_fimc_is_entity(struct fimc_md *fmd, struct fimc_is *is)
+{
+ struct v4l2_subdev *sd = &is->isp.subdev;
+ struct exynos_media_pipeline *ep;
+ int ret;
+
+ /* Allocate pipeline object for the ISP capture video node. */
+ ep = fimc_md_pipeline_create(fmd);
+ if (!ep)
+ return -ENOMEM;
+
+ v4l2_set_subdev_hostdata(sd, ep);
+
+ ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
+ if (ret) {
+ v4l2_err(&fmd->v4l2_dev,
+ "Failed to register FIMC-ISP (%d)\n", ret);
+ return ret;
+ }
+
+ fmd->fimc_is = is;
+ return 0;
+}
+
+static int fimc_md_register_platform_entity(struct fimc_md *fmd,
+ struct platform_device *pdev,
+ int plat_entity)
+{
+ struct device *dev = &pdev->dev;
+ int ret = -EPROBE_DEFER;
+ void *drvdata;
+
+ /* Lock to ensure dev->driver won't change. */
+ device_lock(dev);
+
+ if (!dev->driver || !try_module_get(dev->driver->owner))
+ goto dev_unlock;
+
+ drvdata = dev_get_drvdata(dev);
+ /* Some subdev didn't probe successfully id drvdata is NULL */
+ if (drvdata) {
+ switch (plat_entity) {
+ case IDX_FIMC:
+ ret = register_fimc_entity(fmd, drvdata);
+ break;
+ case IDX_FLITE:
+ ret = register_fimc_lite_entity(fmd, drvdata);
+ break;
+ case IDX_CSIS:
+ ret = register_csis_entity(fmd, pdev, drvdata);
+ break;
+ case IDX_IS_ISP:
+ ret = register_fimc_is_entity(fmd, drvdata);
+ break;
+ default:
+ ret = -ENODEV;
+ }
+ }
+
+ module_put(dev->driver->owner);
+dev_unlock:
+ device_unlock(dev);
+ if (ret == -EPROBE_DEFER)
+ dev_info(&fmd->pdev->dev, "deferring %s device registration\n",
+ dev_name(dev));
+ else if (ret < 0)
+ dev_err(&fmd->pdev->dev, "%s device registration failed (%d)\n",
+ dev_name(dev), ret);
+ return ret;
+}
+
+/* Register FIMC, FIMC-LITE and CSIS media entities */
+static int fimc_md_register_platform_entities(struct fimc_md *fmd,
+ struct device_node *parent)
+{
+ struct device_node *node;
+ int ret = 0;
+
+ for_each_available_child_of_node(parent, node) {
+ struct platform_device *pdev;
+ int plat_entity = -1;
+
+ pdev = of_find_device_by_node(node);
+ if (!pdev)
+ continue;
+
+ /* If driver of any entity isn't ready try all again later. */
+ if (of_node_name_eq(node, CSIS_OF_NODE_NAME))
+ plat_entity = IDX_CSIS;
+ else if (of_node_name_eq(node, FIMC_IS_OF_NODE_NAME))
+ plat_entity = IDX_IS_ISP;
+ else if (of_node_name_eq(node, FIMC_LITE_OF_NODE_NAME))
+ plat_entity = IDX_FLITE;
+ else if (of_node_name_eq(node, FIMC_OF_NODE_NAME) &&
+ !of_property_read_bool(node, "samsung,lcd-wb"))
+ plat_entity = IDX_FIMC;
+
+ if (plat_entity >= 0)
+ ret = fimc_md_register_platform_entity(fmd, pdev,
+ plat_entity);
+ put_device(&pdev->dev);
+ if (ret < 0) {
+ of_node_put(node);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void fimc_md_unregister_entities(struct fimc_md *fmd)
+{
+ int i;
+
+ for (i = 0; i < FIMC_MAX_DEVS; i++) {
+ struct fimc_dev *dev = fmd->fimc[i];
+ if (dev == NULL)
+ continue;
+ v4l2_device_unregister_subdev(&dev->vid_cap.subdev);
+ dev->vid_cap.ve.pipe = NULL;
+ fmd->fimc[i] = NULL;
+ }
+ for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
+ struct fimc_lite *dev = fmd->fimc_lite[i];
+ if (dev == NULL)
+ continue;
+ v4l2_device_unregister_subdev(&dev->subdev);
+ dev->ve.pipe = NULL;
+ fmd->fimc_lite[i] = NULL;
+ }
+ for (i = 0; i < CSIS_MAX_ENTITIES; i++) {
+ if (fmd->csis[i].sd == NULL)
+ continue;
+ v4l2_device_unregister_subdev(fmd->csis[i].sd);
+ fmd->csis[i].sd = NULL;
+ }
+
+ if (fmd->fimc_is)
+ v4l2_device_unregister_subdev(&fmd->fimc_is->isp.subdev);
+
+ v4l2_info(&fmd->v4l2_dev, "Unregistered all entities\n");
+}
+
+/**
+ * __fimc_md_create_fimc_sink_links - create links to all FIMC entities
+ * @fmd: fimc media device
+ * @source: the source entity to create links to all fimc entities from
+ * @sensor: sensor subdev linked to FIMC[fimc_id] entity, may be null
+ * @pad: the source entity pad index
+ * @link_mask: bitmask of the fimc devices for which link should be enabled
+ */
+static int __fimc_md_create_fimc_sink_links(struct fimc_md *fmd,
+ struct media_entity *source,
+ struct v4l2_subdev *sensor,
+ int pad, int link_mask)
+{
+ struct fimc_source_info *si = NULL;
+ struct media_entity *sink;
+ unsigned int flags = 0;
+ int i, ret = 0;
+
+ if (sensor) {
+ si = v4l2_get_subdev_hostdata(sensor);
+ /* Skip direct FIMC links in the logical FIMC-IS sensor path */
+ if (si && si->fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK)
+ ret = 1;
+ }
+
+ for (i = 0; !ret && i < FIMC_MAX_DEVS; i++) {
+ if (!fmd->fimc[i])
+ continue;
+ /*
+ * Some FIMC variants are not fitted with camera capture
+ * interface. Skip creating a link from sensor for those.
+ */
+ if (!fmd->fimc[i]->variant->has_cam_if)
+ continue;
+
+ flags = ((1 << i) & link_mask) ? MEDIA_LNK_FL_ENABLED : 0;
+
+ sink = &fmd->fimc[i]->vid_cap.subdev.entity;
+ ret = media_create_pad_link(source, pad, sink,
+ FIMC_SD_PAD_SINK_CAM, flags);
+ if (ret)
+ return ret;
+
+ /* Notify FIMC capture subdev entity */
+ ret = media_entity_call(sink, link_setup, &sink->pads[0],
+ &source->pads[pad], flags);
+ if (ret)
+ break;
+
+ v4l2_info(&fmd->v4l2_dev, "created link [%s] %c> [%s]\n",
+ source->name, flags ? '=' : '-', sink->name);
+ }
+
+ for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
+ if (!fmd->fimc_lite[i])
+ continue;
+
+ sink = &fmd->fimc_lite[i]->subdev.entity;
+ ret = media_create_pad_link(source, pad, sink,
+ FLITE_SD_PAD_SINK, 0);
+ if (ret)
+ return ret;
+
+ /* Notify FIMC-LITE subdev entity */
+ ret = media_entity_call(sink, link_setup, &sink->pads[0],
+ &source->pads[pad], 0);
+ if (ret)
+ break;
+
+ v4l2_info(&fmd->v4l2_dev, "created link [%s] -> [%s]\n",
+ source->name, sink->name);
+ }
+ return 0;
+}
+
+/* Create links from FIMC-LITE source pads to other entities */
+static int __fimc_md_create_flite_source_links(struct fimc_md *fmd)
+{
+ struct media_entity *source, *sink;
+ int i, ret = 0;
+
+ for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
+ struct fimc_lite *fimc = fmd->fimc_lite[i];
+
+ if (fimc == NULL)
+ continue;
+
+ source = &fimc->subdev.entity;
+ sink = &fimc->ve.vdev.entity;
+ /* FIMC-LITE's subdev and video node */
+ ret = media_create_pad_link(source, FLITE_SD_PAD_SOURCE_DMA,
+ sink, 0, 0);
+ if (ret)
+ break;
+ /* Link from FIMC-LITE to IS-ISP subdev */
+ sink = &fmd->fimc_is->isp.subdev.entity;
+ ret = media_create_pad_link(source, FLITE_SD_PAD_SOURCE_ISP,
+ sink, 0, 0);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+/* Create FIMC-IS links */
+static int __fimc_md_create_fimc_is_links(struct fimc_md *fmd)
+{
+ struct fimc_isp *isp = &fmd->fimc_is->isp;
+ struct media_entity *source, *sink;
+ int i, ret;
+
+ source = &isp->subdev.entity;
+
+ for (i = 0; i < FIMC_MAX_DEVS; i++) {
+ if (fmd->fimc[i] == NULL)
+ continue;
+
+ /* Link from FIMC-IS-ISP subdev to FIMC */
+ sink = &fmd->fimc[i]->vid_cap.subdev.entity;
+ ret = media_create_pad_link(source, FIMC_ISP_SD_PAD_SRC_FIFO,
+ sink, FIMC_SD_PAD_SINK_FIFO, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Link from FIMC-IS-ISP subdev to fimc-is-isp.capture video node */
+ sink = &isp->video_capture.ve.vdev.entity;
+
+ /* Skip this link if the fimc-is-isp video node driver isn't built-in */
+ if (sink->num_pads == 0)
+ return 0;
+
+ return media_create_pad_link(source, FIMC_ISP_SD_PAD_SRC_DMA,
+ sink, 0, 0);
+}
+
+/**
+ * fimc_md_create_links - create default links between registered entities
+ * @fmd: fimc media device
+ *
+ * Parallel interface sensor entities are connected directly to FIMC capture
+ * entities. The sensors using MIPI CSIS bus are connected through immutable
+ * link with CSI receiver entity specified by mux_id. Any registered CSIS
+ * entity has a link to each registered FIMC capture entity. Enabled links
+ * are created by default between each subsequent registered sensor and
+ * subsequent FIMC capture entity. The number of default active links is
+ * determined by the number of available sensors or FIMC entities,
+ * whichever is less.
+ */
+static int fimc_md_create_links(struct fimc_md *fmd)
+{
+ struct v4l2_subdev *csi_sensors[CSIS_MAX_ENTITIES] = { NULL };
+ struct v4l2_subdev *sensor, *csis;
+ struct fimc_source_info *pdata;
+ struct media_entity *source, *sink;
+ int i, pad, fimc_id = 0, ret = 0;
+ u32 flags, link_mask = 0;
+
+ for (i = 0; i < fmd->num_sensors; i++) {
+ if (fmd->sensor[i].subdev == NULL)
+ continue;
+
+ sensor = fmd->sensor[i].subdev;
+ pdata = v4l2_get_subdev_hostdata(sensor);
+ if (!pdata)
+ continue;
+
+ source = NULL;
+
+ switch (pdata->sensor_bus_type) {
+ case FIMC_BUS_TYPE_MIPI_CSI2:
+ if (WARN(pdata->mux_id >= CSIS_MAX_ENTITIES,
+ "Wrong CSI channel id: %d\n", pdata->mux_id))
+ return -EINVAL;
+
+ csis = fmd->csis[pdata->mux_id].sd;
+ if (WARN(csis == NULL,
+ "MIPI-CSI interface specified but s5p-csis module is not loaded!\n"))
+ return -EINVAL;
+
+ pad = sensor->entity.num_pads - 1;
+ ret = media_create_pad_link(&sensor->entity, pad,
+ &csis->entity, CSIS_PAD_SINK,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ return ret;
+
+ v4l2_info(&fmd->v4l2_dev, "created link [%s] => [%s]\n",
+ sensor->entity.name, csis->entity.name);
+
+ source = NULL;
+ csi_sensors[pdata->mux_id] = sensor;
+ break;
+
+ case FIMC_BUS_TYPE_ITU_601...FIMC_BUS_TYPE_ITU_656:
+ source = &sensor->entity;
+ pad = 0;
+ break;
+
+ default:
+ v4l2_err(&fmd->v4l2_dev, "Wrong bus_type: %x\n",
+ pdata->sensor_bus_type);
+ return -EINVAL;
+ }
+ if (source == NULL)
+ continue;
+
+ link_mask = 1 << fimc_id++;
+ ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor,
+ pad, link_mask);
+ }
+
+ for (i = 0; i < CSIS_MAX_ENTITIES; i++) {
+ if (fmd->csis[i].sd == NULL)
+ continue;
+
+ source = &fmd->csis[i].sd->entity;
+ pad = CSIS_PAD_SOURCE;
+ sensor = csi_sensors[i];
+
+ link_mask = 1 << fimc_id++;
+ ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor,
+ pad, link_mask);
+ }
+
+ /* Create immutable links between each FIMC's subdev and video node */
+ flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
+ for (i = 0; i < FIMC_MAX_DEVS; i++) {
+ if (!fmd->fimc[i])
+ continue;
+
+ source = &fmd->fimc[i]->vid_cap.subdev.entity;
+ sink = &fmd->fimc[i]->vid_cap.ve.vdev.entity;
+
+ ret = media_create_pad_link(source, FIMC_SD_PAD_SOURCE,
+ sink, 0, flags);
+ if (ret)
+ break;
+ }
+
+ ret = __fimc_md_create_flite_source_links(fmd);
+ if (ret < 0)
+ return ret;
+
+ if (fmd->use_isp)
+ ret = __fimc_md_create_fimc_is_links(fmd);
+
+ return ret;
+}
+
+/*
+ * The peripheral sensor and CAM_BLK (PIXELASYNCMx) clocks management.
+ */
+static void fimc_md_put_clocks(struct fimc_md *fmd)
+{
+ int i = FIMC_MAX_CAMCLKS;
+
+ while (--i >= 0) {
+ if (IS_ERR(fmd->camclk[i].clock))
+ continue;
+ clk_put(fmd->camclk[i].clock);
+ fmd->camclk[i].clock = ERR_PTR(-EINVAL);
+ }
+
+ /* Writeback (PIXELASYNCMx) clocks */
+ for (i = 0; i < FIMC_MAX_WBCLKS; i++) {
+ if (IS_ERR(fmd->wbclk[i]))
+ continue;
+ clk_put(fmd->wbclk[i]);
+ fmd->wbclk[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int fimc_md_get_clocks(struct fimc_md *fmd)
+{
+ struct device *dev = &fmd->pdev->dev;
+ char clk_name[32];
+ struct clk *clock;
+ int i, ret = 0;
+
+ for (i = 0; i < FIMC_MAX_CAMCLKS; i++)
+ fmd->camclk[i].clock = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < FIMC_MAX_CAMCLKS; i++) {
+ snprintf(clk_name, sizeof(clk_name), "sclk_cam%u", i);
+ clock = clk_get(dev, clk_name);
+
+ if (IS_ERR(clock)) {
+ dev_err(dev, "Failed to get clock: %s\n", clk_name);
+ ret = PTR_ERR(clock);
+ break;
+ }
+ fmd->camclk[i].clock = clock;
+ }
+ if (ret)
+ fimc_md_put_clocks(fmd);
+
+ if (!fmd->use_isp)
+ return 0;
+ /*
+ * For now get only PIXELASYNCM1 clock (Writeback B/ISP),
+ * leave PIXELASYNCM0 out for the LCD Writeback driver.
+ */
+ fmd->wbclk[CLK_IDX_WB_A] = ERR_PTR(-EINVAL);
+
+ for (i = CLK_IDX_WB_B; i < FIMC_MAX_WBCLKS; i++) {
+ snprintf(clk_name, sizeof(clk_name), "pxl_async%u", i);
+ clock = clk_get(dev, clk_name);
+ if (IS_ERR(clock)) {
+ v4l2_err(&fmd->v4l2_dev, "Failed to get clock: %s\n",
+ clk_name);
+ ret = PTR_ERR(clock);
+ break;
+ }
+ fmd->wbclk[i] = clock;
+ }
+ if (ret)
+ fimc_md_put_clocks(fmd);
+
+ return ret;
+}
+
+static int __fimc_md_modify_pipeline(struct media_entity *entity, bool enable)
+{
+ struct exynos_video_entity *ve;
+ struct fimc_pipeline *p;
+ struct video_device *vdev;
+ int ret;
+
+ vdev = media_entity_to_video_device(entity);
+ if (vdev->entity.use_count == 0)
+ return 0;
+
+ ve = vdev_to_exynos_video_entity(vdev);
+ p = to_fimc_pipeline(ve->pipe);
+ /*
+ * Nothing to do if we are disabling the pipeline, some link
+ * has been disconnected and p->subdevs array is cleared now.
+ */
+ if (!enable && p->subdevs[IDX_SENSOR] == NULL)
+ return 0;
+
+ if (enable)
+ ret = __fimc_pipeline_open(ve->pipe, entity, true);
+ else
+ ret = __fimc_pipeline_close(ve->pipe);
+
+ if (ret == 0 && !enable)
+ memset(p->subdevs, 0, sizeof(p->subdevs));
+
+ return ret;
+}
+
+/* Locking: called with entity->graph_obj.mdev->graph_mutex mutex held. */
+static int __fimc_md_modify_pipelines(struct media_entity *entity, bool enable,
+ struct media_graph *graph)
+{
+ struct media_entity *entity_err = entity;
+ int ret;
+
+ /*
+ * Walk current graph and call the pipeline open/close routine for each
+ * opened video node that belongs to the graph of entities connected
+ * through active links. This is needed as we cannot power on/off the
+ * subdevs in random order.
+ */
+ media_graph_walk_start(graph, entity);
+
+ while ((entity = media_graph_walk_next(graph))) {
+ if (!is_media_entity_v4l2_video_device(entity))
+ continue;
+
+ ret = __fimc_md_modify_pipeline(entity, enable);
+
+ if (ret < 0)
+ goto err;
+ }
+
+ return 0;
+
+err:
+ media_graph_walk_start(graph, entity_err);
+
+ while ((entity_err = media_graph_walk_next(graph))) {
+ if (!is_media_entity_v4l2_video_device(entity_err))
+ continue;
+
+ __fimc_md_modify_pipeline(entity_err, !enable);
+
+ if (entity_err == entity)
+ break;
+ }
+
+ return ret;
+}
+
+static int fimc_md_link_notify(struct media_link *link, unsigned int flags,
+ unsigned int notification)
+{
+ struct media_graph *graph =
+ &container_of(link->graph_obj.mdev, struct fimc_md,
+ media_dev)->link_setup_graph;
+ struct media_entity *sink = link->sink->entity;
+ int ret = 0;
+
+ /* Before link disconnection */
+ if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH) {
+ ret = media_graph_walk_init(graph,
+ link->graph_obj.mdev);
+ if (ret)
+ return ret;
+ if (!(flags & MEDIA_LNK_FL_ENABLED))
+ ret = __fimc_md_modify_pipelines(sink, false, graph);
+#if 0
+ else
+ /* TODO: Link state change validation */
+#endif
+ /* After link activation */
+ } else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH) {
+ if (link->flags & MEDIA_LNK_FL_ENABLED)
+ ret = __fimc_md_modify_pipelines(sink, true, graph);
+ media_graph_walk_cleanup(graph);
+ }
+
+ return ret ? -EPIPE : 0;
+}
+
+static const struct media_device_ops fimc_md_ops = {
+ .link_notify = fimc_md_link_notify,
+};
+
+static ssize_t subdev_conf_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fimc_md *fmd = dev_get_drvdata(dev);
+
+ if (fmd->user_subdev_api)
+ return strscpy(buf, "Sub-device API (sub-dev)\n", PAGE_SIZE);
+
+ return strscpy(buf, "V4L2 video node only API (vid-dev)\n", PAGE_SIZE);
+}
+
+static ssize_t subdev_conf_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fimc_md *fmd = dev_get_drvdata(dev);
+ bool subdev_api;
+ int i;
+
+ if (!strcmp(buf, "vid-dev\n"))
+ subdev_api = false;
+ else if (!strcmp(buf, "sub-dev\n"))
+ subdev_api = true;
+ else
+ return count;
+
+ fmd->user_subdev_api = subdev_api;
+ for (i = 0; i < FIMC_MAX_DEVS; i++)
+ if (fmd->fimc[i])
+ fmd->fimc[i]->vid_cap.user_subdev_api = subdev_api;
+ return count;
+}
+/*
+ * This device attribute is to select video pipeline configuration method.
+ * There are following valid values:
+ * vid-dev - for V4L2 video node API only, subdevice will be configured
+ * by the host driver.
+ * sub-dev - for media controller API, subdevs must be configured in user
+ * space before starting streaming.
+ */
+static DEVICE_ATTR_RW(subdev_conf_mode);
+
+static int cam_clk_prepare(struct clk_hw *hw)
+{
+ struct cam_clk *camclk = to_cam_clk(hw);
+
+ if (camclk->fmd->pmf == NULL)
+ return -ENODEV;
+
+ return pm_runtime_resume_and_get(camclk->fmd->pmf);
+}
+
+static void cam_clk_unprepare(struct clk_hw *hw)
+{
+ struct cam_clk *camclk = to_cam_clk(hw);
+
+ if (camclk->fmd->pmf == NULL)
+ return;
+
+ pm_runtime_put_sync(camclk->fmd->pmf);
+}
+
+static const struct clk_ops cam_clk_ops = {
+ .prepare = cam_clk_prepare,
+ .unprepare = cam_clk_unprepare,
+};
+
+static void fimc_md_unregister_clk_provider(struct fimc_md *fmd)
+{
+ struct cam_clk_provider *cp = &fmd->clk_provider;
+ unsigned int i;
+
+ if (cp->of_node)
+ of_clk_del_provider(cp->of_node);
+
+ for (i = 0; i < cp->num_clocks; i++)
+ clk_unregister(cp->clks[i]);
+}
+
+static int fimc_md_register_clk_provider(struct fimc_md *fmd)
+{
+ struct cam_clk_provider *cp = &fmd->clk_provider;
+ struct device *dev = &fmd->pdev->dev;
+ int i, ret;
+
+ for (i = 0; i < FIMC_MAX_CAMCLKS; i++) {
+ struct cam_clk *camclk = &cp->camclk[i];
+ struct clk_init_data init;
+ const char *p_name;
+
+ ret = of_property_read_string_index(dev->of_node,
+ "clock-output-names", i, &init.name);
+ if (ret < 0)
+ break;
+
+ p_name = __clk_get_name(fmd->camclk[i].clock);
+
+ /* It's safe since clk_register() will duplicate the string. */
+ init.parent_names = &p_name;
+ init.num_parents = 1;
+ init.ops = &cam_clk_ops;
+ init.flags = CLK_SET_RATE_PARENT;
+ camclk->hw.init = &init;
+ camclk->fmd = fmd;
+
+ cp->clks[i] = clk_register(NULL, &camclk->hw);
+ if (IS_ERR(cp->clks[i])) {
+ dev_err(dev, "failed to register clock: %s (%ld)\n",
+ init.name, PTR_ERR(cp->clks[i]));
+ ret = PTR_ERR(cp->clks[i]);
+ goto err;
+ }
+ cp->num_clocks++;
+ }
+
+ if (cp->num_clocks == 0) {
+ dev_warn(dev, "clk provider not registered\n");
+ return 0;
+ }
+
+ cp->clk_data.clks = cp->clks;
+ cp->clk_data.clk_num = cp->num_clocks;
+ cp->of_node = dev->of_node;
+ ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
+ &cp->clk_data);
+ if (ret == 0)
+ return 0;
+err:
+ fimc_md_unregister_clk_provider(fmd);
+ return ret;
+}
+
+static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_connection *asd)
+{
+ struct fimc_md *fmd = notifier_to_fimc_md(notifier);
+ struct fimc_sensor_info *si = NULL;
+ int i;
+
+ /* Find platform data for this sensor subdev */
+ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++)
+ if (fmd->sensor[i].asd == asd)
+ si = &fmd->sensor[i];
+
+ if (si == NULL)
+ return -EINVAL;
+
+ v4l2_set_subdev_hostdata(subdev, &si->pdata);
+
+ if (si->pdata.fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK)
+ subdev->grp_id = GRP_ID_FIMC_IS_SENSOR;
+ else
+ subdev->grp_id = GRP_ID_SENSOR;
+
+ si->subdev = subdev;
+
+ v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n",
+ subdev->name, fmd->num_sensors);
+
+ fmd->num_sensors++;
+
+ return 0;
+}
+
+static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+ struct fimc_md *fmd = notifier_to_fimc_md(notifier);
+ int ret;
+
+ mutex_lock(&fmd->media_dev.graph_mutex);
+
+ ret = fimc_md_create_links(fmd);
+ if (ret < 0)
+ goto unlock;
+
+ ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev);
+unlock:
+ mutex_unlock(&fmd->media_dev.graph_mutex);
+ if (ret < 0)
+ return ret;
+
+ return media_device_register(&fmd->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations subdev_notifier_ops = {
+ .bound = subdev_notifier_bound,
+ .complete = subdev_notifier_complete,
+};
+
+static int fimc_md_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct v4l2_device *v4l2_dev;
+ struct pinctrl *pinctrl;
+ struct fimc_md *fmd;
+ int ret;
+
+ fmd = devm_kzalloc(dev, sizeof(*fmd), GFP_KERNEL);
+ if (!fmd)
+ return -ENOMEM;
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret < 0)
+ return -ENOMEM;
+
+ spin_lock_init(&fmd->slock);
+ INIT_LIST_HEAD(&fmd->pipelines);
+ fmd->pdev = pdev;
+
+ strscpy(fmd->media_dev.model, "Samsung S5P FIMC",
+ sizeof(fmd->media_dev.model));
+ fmd->media_dev.ops = &fimc_md_ops;
+ fmd->media_dev.dev = dev;
+
+ v4l2_dev = &fmd->v4l2_dev;
+ v4l2_dev->mdev = &fmd->media_dev;
+ v4l2_dev->notify = fimc_sensor_notify;
+ strscpy(v4l2_dev->name, "s5p-fimc-md", sizeof(v4l2_dev->name));
+
+ fmd->use_isp = fimc_md_is_isp_available(dev->of_node);
+ fmd->user_subdev_api = true;
+
+ media_device_init(&fmd->media_dev);
+
+ ret = v4l2_device_register(dev, &fmd->v4l2_dev);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret);
+ goto err_md;
+ }
+
+ ret = fimc_md_get_clocks(fmd);
+ if (ret)
+ goto err_v4l2dev;
+
+ pinctrl = devm_pinctrl_get(dev);
+ if (IS_ERR(pinctrl))
+ dev_dbg(dev, "Failed to get pinctrl: %pe\n", pinctrl);
+
+ platform_set_drvdata(pdev, fmd);
+
+ v4l2_async_nf_init(&fmd->subdev_notifier, &fmd->v4l2_dev);
+
+ ret = fimc_md_register_platform_entities(fmd, dev->of_node);
+ if (ret)
+ goto err_clk;
+
+ ret = fimc_md_register_sensor_entities(fmd);
+ if (ret)
+ goto err_m_ent;
+
+ ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode);
+ if (ret)
+ goto err_cleanup;
+ /*
+ * FIMC platform devices need to be registered before the sclk_cam
+ * clocks provider, as one of these devices needs to be activated
+ * to enable the clock.
+ */
+ ret = fimc_md_register_clk_provider(fmd);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "clock provider registration failed\n");
+ goto err_attr;
+ }
+
+ if (fmd->num_sensors > 0) {
+ fmd->subdev_notifier.ops = &subdev_notifier_ops;
+ fmd->num_sensors = 0;
+
+ ret = v4l2_async_nf_register(&fmd->subdev_notifier);
+ if (ret)
+ goto err_clk_p;
+ }
+
+ return 0;
+
+err_clk_p:
+ fimc_md_unregister_clk_provider(fmd);
+err_attr:
+ device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
+err_cleanup:
+ v4l2_async_nf_cleanup(&fmd->subdev_notifier);
+err_m_ent:
+ fimc_md_unregister_entities(fmd);
+err_clk:
+ fimc_md_put_clocks(fmd);
+err_v4l2dev:
+ v4l2_device_unregister(&fmd->v4l2_dev);
+err_md:
+ media_device_cleanup(&fmd->media_dev);
+ return ret;
+}
+
+static void fimc_md_remove(struct platform_device *pdev)
+{
+ struct fimc_md *fmd = platform_get_drvdata(pdev);
+
+ if (!fmd)
+ return;
+
+ fimc_md_unregister_clk_provider(fmd);
+ v4l2_async_nf_unregister(&fmd->subdev_notifier);
+ v4l2_async_nf_cleanup(&fmd->subdev_notifier);
+
+ v4l2_device_unregister(&fmd->v4l2_dev);
+ device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
+ fimc_md_unregister_entities(fmd);
+ fimc_md_pipelines_free(fmd);
+ media_device_unregister(&fmd->media_dev);
+ media_device_cleanup(&fmd->media_dev);
+ fimc_md_put_clocks(fmd);
+}
+
+static const struct platform_device_id fimc_driver_ids[] __always_unused = {
+ { .name = "s5p-fimc-md" },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, fimc_driver_ids);
+
+static const struct of_device_id fimc_md_of_match[] = {
+ { .compatible = "samsung,fimc" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, fimc_md_of_match);
+
+static struct platform_driver fimc_md_driver = {
+ .probe = fimc_md_probe,
+ .remove_new = fimc_md_remove,
+ .driver = {
+ .of_match_table = of_match_ptr(fimc_md_of_match),
+ .name = "s5p-fimc-md",
+ }
+};
+
+static int __init fimc_md_init(void)
+{
+ int ret;
+
+ request_module("s5p-csis");
+ ret = fimc_register_driver();
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&fimc_md_driver);
+ if (ret)
+ fimc_unregister_driver();
+
+ return ret;
+}
+
+static void __exit fimc_md_exit(void)
+{
+ platform_driver_unregister(&fimc_md_driver);
+ fimc_unregister_driver();
+}
+
+module_init(fimc_md_init);
+module_exit(fimc_md_exit);
+
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_DESCRIPTION("S5P FIMC camera host interface/video postprocessor driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("2.0.1");
diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.h b/drivers/media/platform/samsung/exynos4-is/media-dev.h
new file mode 100644
index 0000000000..786264cf79
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/media-dev.h
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd.
+ */
+
+#ifndef FIMC_MDEVICE_H_
+#define FIMC_MDEVICE_H_
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/drv-intf/exynos-fimc.h>
+
+#include "fimc-core.h"
+#include "fimc-lite.h"
+#include "mipi-csis.h"
+
+#define FIMC_OF_NODE_NAME "fimc"
+#define FIMC_LITE_OF_NODE_NAME "fimc-lite"
+#define FIMC_IS_OF_NODE_NAME "fimc-is"
+#define CSIS_OF_NODE_NAME "csis"
+
+#define FIMC_MAX_SENSORS 4
+#define FIMC_MAX_CAMCLKS 2
+#define DEFAULT_SENSOR_CLK_FREQ 24000000U
+
+/* LCD/ISP Writeback clocks (PIXELASYNCMx) */
+enum {
+ CLK_IDX_WB_A,
+ CLK_IDX_WB_B,
+ FIMC_MAX_WBCLKS
+};
+
+enum fimc_subdev_index {
+ IDX_SENSOR,
+ IDX_CSIS,
+ IDX_FLITE,
+ IDX_IS_ISP,
+ IDX_FIMC,
+ IDX_MAX,
+};
+
+/*
+ * This structure represents a chain of media entities, including a data
+ * source entity (e.g. an image sensor subdevice), a data capture entity
+ * - a video capture device node and any remaining entities.
+ */
+struct fimc_pipeline {
+ struct exynos_media_pipeline ep;
+ struct list_head list;
+ struct media_entity *vdev_entity;
+ struct v4l2_subdev *subdevs[IDX_MAX];
+};
+
+#define to_fimc_pipeline(_ep) container_of(_ep, struct fimc_pipeline, ep)
+
+struct fimc_csis_info {
+ struct v4l2_subdev *sd;
+ int id;
+};
+
+struct fimc_camclk_info {
+ struct clk *clock;
+ int use_count;
+ unsigned long frequency;
+};
+
+/**
+ * struct fimc_sensor_info - image data source subdev information
+ * @pdata: sensor's attributes passed as media device's platform data
+ * @asd: asynchronous subdev registration data structure
+ * @subdev: image sensor v4l2 subdev
+ * @host: fimc device the sensor is currently linked to
+ *
+ * This data structure applies to image sensor and the writeback subdevs.
+ */
+struct fimc_sensor_info {
+ struct fimc_source_info pdata;
+ struct v4l2_async_connection *asd;
+ struct v4l2_subdev *subdev;
+ struct fimc_dev *host;
+};
+
+struct cam_clk {
+ struct clk_hw hw;
+ struct fimc_md *fmd;
+};
+#define to_cam_clk(_hw) container_of(_hw, struct cam_clk, hw)
+
+/**
+ * struct fimc_md - fimc media device information
+ * @csis: MIPI CSIS subdevs data
+ * @sensor: array of registered sensor subdevs
+ * @num_sensors: actual number of registered sensors
+ * @camclk: external sensor clock information
+ * @wbclk: external writeback clock information
+ * @fimc_lite: array of registered fimc-lite devices
+ * @fimc: array of registered fimc devices
+ * @fimc_is: fimc-is data structure
+ * @use_isp: set to true when FIMC-IS subsystem is used
+ * @pmf: handle to the CAMCLK clock control FIMC helper device
+ * @media_dev: top level media device
+ * @v4l2_dev: top level v4l2_device holding up the subdevs
+ * @pdev: platform device this media device is hooked up into
+ * @clk_provider: CAMCLK clock provider structure
+ * @subdev_notifier: notifier for the subdevs
+ * @user_subdev_api: true if subdevs are not configured by the host driver
+ * @slock: spinlock protecting @sensor array
+ * @pipelines: list of pipelines
+ * @link_setup_graph: graph iterator
+ */
+struct fimc_md {
+ struct fimc_csis_info csis[CSIS_MAX_ENTITIES];
+ struct fimc_sensor_info sensor[FIMC_MAX_SENSORS];
+ int num_sensors;
+ struct fimc_camclk_info camclk[FIMC_MAX_CAMCLKS];
+ struct clk *wbclk[FIMC_MAX_WBCLKS];
+ struct fimc_lite *fimc_lite[FIMC_LITE_MAX_DEVS];
+ struct fimc_dev *fimc[FIMC_MAX_DEVS];
+ struct fimc_is *fimc_is;
+ bool use_isp;
+ struct device *pmf;
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct platform_device *pdev;
+
+ struct cam_clk_provider {
+ struct clk *clks[FIMC_MAX_CAMCLKS];
+ struct clk_onecell_data clk_data;
+ struct device_node *of_node;
+ struct cam_clk camclk[FIMC_MAX_CAMCLKS];
+ int num_clocks;
+ } clk_provider;
+
+ struct v4l2_async_notifier subdev_notifier;
+
+ bool user_subdev_api;
+ spinlock_t slock;
+ struct list_head pipelines;
+ struct media_graph link_setup_graph;
+};
+
+static inline
+struct fimc_sensor_info *source_to_sensor_info(struct fimc_source_info *si)
+{
+ return container_of(si, struct fimc_sensor_info, pdata);
+}
+
+static inline struct fimc_md *entity_to_fimc_mdev(struct media_entity *me)
+{
+ return me->graph_obj.mdev == NULL ? NULL :
+ container_of(me->graph_obj.mdev, struct fimc_md, media_dev);
+}
+
+static inline struct fimc_md *notifier_to_fimc_md(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct fimc_md, subdev_notifier);
+}
+
+static inline void fimc_md_graph_lock(struct exynos_video_entity *ve)
+{
+ mutex_lock(&ve->vdev.entity.graph_obj.mdev->graph_mutex);
+}
+
+static inline void fimc_md_graph_unlock(struct exynos_video_entity *ve)
+{
+ mutex_unlock(&ve->vdev.entity.graph_obj.mdev->graph_mutex);
+}
+
+int fimc_md_set_camclk(struct v4l2_subdev *sd, bool on);
+
+#ifdef CONFIG_OF
+static inline bool fimc_md_is_isp_available(struct device_node *node)
+{
+ node = of_get_child_by_name(node, FIMC_IS_OF_NODE_NAME);
+ return node ? of_device_is_available(node) : false;
+}
+#else
+#define fimc_md_is_isp_available(node) (false)
+#endif /* CONFIG_OF */
+
+static inline struct v4l2_subdev *__fimc_md_get_subdev(
+ struct exynos_media_pipeline *ep,
+ unsigned int index)
+{
+ struct fimc_pipeline *p = to_fimc_pipeline(ep);
+
+ if (!p || index >= IDX_MAX)
+ return NULL;
+ else
+ return p->subdevs[index];
+}
+
+#endif
diff --git a/drivers/media/platform/samsung/exynos4-is/mipi-csis.c b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c
new file mode 100644
index 0000000000..686ca8753b
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung S5P/EXYNOS SoC series MIPI-CSI receiver driver
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/memory.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/videodev2.h>
+#include <media/drv-intf/exynos-fimc.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#include "mipi-csis.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level (0-2)");
+
+/* Register map definition */
+
+/* CSIS global control */
+#define S5PCSIS_CTRL 0x00
+#define S5PCSIS_CTRL_DPDN_DEFAULT (0 << 31)
+#define S5PCSIS_CTRL_DPDN_SWAP (1UL << 31)
+#define S5PCSIS_CTRL_ALIGN_32BIT (1 << 20)
+#define S5PCSIS_CTRL_UPDATE_SHADOW (1 << 16)
+#define S5PCSIS_CTRL_WCLK_EXTCLK (1 << 8)
+#define S5PCSIS_CTRL_RESET (1 << 4)
+#define S5PCSIS_CTRL_ENABLE (1 << 0)
+
+/* D-PHY control */
+#define S5PCSIS_DPHYCTRL 0x04
+#define S5PCSIS_DPHYCTRL_HSS_MASK (0x1f << 27)
+#define S5PCSIS_DPHYCTRL_ENABLE (0x1f << 0)
+
+#define S5PCSIS_CONFIG 0x08
+#define S5PCSIS_CFG_FMT_YCBCR422_8BIT (0x1e << 2)
+#define S5PCSIS_CFG_FMT_RAW8 (0x2a << 2)
+#define S5PCSIS_CFG_FMT_RAW10 (0x2b << 2)
+#define S5PCSIS_CFG_FMT_RAW12 (0x2c << 2)
+/* User defined formats, x = 1...4 */
+#define S5PCSIS_CFG_FMT_USER(x) ((0x30 + x - 1) << 2)
+#define S5PCSIS_CFG_FMT_MASK (0x3f << 2)
+#define S5PCSIS_CFG_NR_LANE_MASK 3
+
+/* Interrupt mask */
+#define S5PCSIS_INTMSK 0x10
+#define S5PCSIS_INTMSK_EVEN_BEFORE (1UL << 31)
+#define S5PCSIS_INTMSK_EVEN_AFTER (1 << 30)
+#define S5PCSIS_INTMSK_ODD_BEFORE (1 << 29)
+#define S5PCSIS_INTMSK_ODD_AFTER (1 << 28)
+#define S5PCSIS_INTMSK_FRAME_START (1 << 27)
+#define S5PCSIS_INTMSK_FRAME_END (1 << 26)
+#define S5PCSIS_INTMSK_ERR_SOT_HS (1 << 12)
+#define S5PCSIS_INTMSK_ERR_LOST_FS (1 << 5)
+#define S5PCSIS_INTMSK_ERR_LOST_FE (1 << 4)
+#define S5PCSIS_INTMSK_ERR_OVER (1 << 3)
+#define S5PCSIS_INTMSK_ERR_ECC (1 << 2)
+#define S5PCSIS_INTMSK_ERR_CRC (1 << 1)
+#define S5PCSIS_INTMSK_ERR_UNKNOWN (1 << 0)
+#define S5PCSIS_INTMSK_EXYNOS4_EN_ALL 0xf000103f
+#define S5PCSIS_INTMSK_EXYNOS5_EN_ALL 0xfc00103f
+
+/* Interrupt source */
+#define S5PCSIS_INTSRC 0x14
+#define S5PCSIS_INTSRC_EVEN_BEFORE (1UL << 31)
+#define S5PCSIS_INTSRC_EVEN_AFTER (1 << 30)
+#define S5PCSIS_INTSRC_EVEN (0x3 << 30)
+#define S5PCSIS_INTSRC_ODD_BEFORE (1 << 29)
+#define S5PCSIS_INTSRC_ODD_AFTER (1 << 28)
+#define S5PCSIS_INTSRC_ODD (0x3 << 28)
+#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xf << 28)
+#define S5PCSIS_INTSRC_FRAME_START (1 << 27)
+#define S5PCSIS_INTSRC_FRAME_END (1 << 26)
+#define S5PCSIS_INTSRC_ERR_SOT_HS (0xf << 12)
+#define S5PCSIS_INTSRC_ERR_LOST_FS (1 << 5)
+#define S5PCSIS_INTSRC_ERR_LOST_FE (1 << 4)
+#define S5PCSIS_INTSRC_ERR_OVER (1 << 3)
+#define S5PCSIS_INTSRC_ERR_ECC (1 << 2)
+#define S5PCSIS_INTSRC_ERR_CRC (1 << 1)
+#define S5PCSIS_INTSRC_ERR_UNKNOWN (1 << 0)
+#define S5PCSIS_INTSRC_ERRORS 0xf03f
+
+/* Pixel resolution */
+#define S5PCSIS_RESOL 0x2c
+#define CSIS_MAX_PIX_WIDTH 0xffff
+#define CSIS_MAX_PIX_HEIGHT 0xffff
+
+/* Non-image packet data buffers */
+#define S5PCSIS_PKTDATA_ODD 0x2000
+#define S5PCSIS_PKTDATA_EVEN 0x3000
+#define S5PCSIS_PKTDATA_SIZE SZ_4K
+
+enum {
+ CSIS_CLK_MUX,
+ CSIS_CLK_GATE,
+};
+
+static char *csi_clock_name[] = {
+ [CSIS_CLK_MUX] = "sclk_csis",
+ [CSIS_CLK_GATE] = "csis",
+};
+#define NUM_CSIS_CLOCKS ARRAY_SIZE(csi_clock_name)
+#define DEFAULT_SCLK_CSIS_FREQ 166000000UL
+
+static const char * const csis_supply_name[] = {
+ "vddcore", /* CSIS Core (1.0V, 1.1V or 1.2V) supply */
+ "vddio", /* CSIS I/O and PLL (1.8V) supply */
+};
+#define CSIS_NUM_SUPPLIES ARRAY_SIZE(csis_supply_name)
+
+enum {
+ ST_POWERED = 1,
+ ST_STREAMING = 2,
+ ST_SUSPENDED = 4,
+};
+
+struct s5pcsis_event {
+ u32 mask;
+ const char * const name;
+ unsigned int counter;
+};
+
+static const struct s5pcsis_event s5pcsis_events[] = {
+ /* Errors */
+ { S5PCSIS_INTSRC_ERR_SOT_HS, "SOT Error" },
+ { S5PCSIS_INTSRC_ERR_LOST_FS, "Lost Frame Start Error" },
+ { S5PCSIS_INTSRC_ERR_LOST_FE, "Lost Frame End Error" },
+ { S5PCSIS_INTSRC_ERR_OVER, "FIFO Overflow Error" },
+ { S5PCSIS_INTSRC_ERR_ECC, "ECC Error" },
+ { S5PCSIS_INTSRC_ERR_CRC, "CRC Error" },
+ { S5PCSIS_INTSRC_ERR_UNKNOWN, "Unknown Error" },
+ /* Non-image data receive events */
+ { S5PCSIS_INTSRC_EVEN_BEFORE, "Non-image data before even frame" },
+ { S5PCSIS_INTSRC_EVEN_AFTER, "Non-image data after even frame" },
+ { S5PCSIS_INTSRC_ODD_BEFORE, "Non-image data before odd frame" },
+ { S5PCSIS_INTSRC_ODD_AFTER, "Non-image data after odd frame" },
+ /* Frame start/end */
+ { S5PCSIS_INTSRC_FRAME_START, "Frame Start" },
+ { S5PCSIS_INTSRC_FRAME_END, "Frame End" },
+};
+#define S5PCSIS_NUM_EVENTS ARRAY_SIZE(s5pcsis_events)
+
+struct csis_pktbuf {
+ u32 *data;
+ unsigned int len;
+};
+
+struct csis_drvdata {
+ /* Mask of all used interrupts in S5PCSIS_INTMSK register */
+ u32 interrupt_mask;
+};
+
+/**
+ * struct csis_state - the driver's internal state data structure
+ * @lock: mutex serializing the subdev and power management operations,
+ * protecting @format and @flags members
+ * @pads: CSIS pads array
+ * @sd: v4l2_subdev associated with CSIS device instance
+ * @index: the hardware instance index
+ * @pdev: CSIS platform device
+ * @phy: pointer to the CSIS generic PHY
+ * @regs: mmapped I/O registers memory
+ * @supplies: CSIS regulator supplies
+ * @clock: CSIS clocks
+ * @irq: requested s5p-mipi-csis irq number
+ * @interrupt_mask: interrupt mask of the all used interrupts
+ * @flags: the state variable for power and streaming control
+ * @clk_frequency: device bus clock frequency
+ * @hs_settle: HS-RX settle time
+ * @num_lanes: number of MIPI-CSI data lanes used
+ * @max_num_lanes: maximum number of MIPI-CSI data lanes supported
+ * @wclk_ext: CSI wrapper clock: 0 - bus clock, 1 - external SCLK_CAM
+ * @csis_fmt: current CSIS pixel format
+ * @format: common media bus format for the source and sink pad
+ * @slock: spinlock protecting structure members below
+ * @pkt_buf: the frame embedded (non-image) data buffer
+ * @events: MIPI-CSIS event (error) counters
+ */
+struct csis_state {
+ struct mutex lock;
+ struct media_pad pads[CSIS_PADS_NUM];
+ struct v4l2_subdev sd;
+ u8 index;
+ struct platform_device *pdev;
+ struct phy *phy;
+ void __iomem *regs;
+ struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES];
+ struct clk *clock[NUM_CSIS_CLOCKS];
+ int irq;
+ u32 interrupt_mask;
+ u32 flags;
+
+ u32 clk_frequency;
+ u32 hs_settle;
+ u32 num_lanes;
+ u32 max_num_lanes;
+ u8 wclk_ext;
+
+ const struct csis_pix_format *csis_fmt;
+ struct v4l2_mbus_framefmt format;
+
+ spinlock_t slock;
+ struct csis_pktbuf pkt_buf;
+ struct s5pcsis_event events[S5PCSIS_NUM_EVENTS];
+};
+
+/**
+ * struct csis_pix_format - CSIS pixel format description
+ * @pix_width_alignment: horizontal pixel alignment, width will be
+ * multiple of 2^pix_width_alignment
+ * @code: corresponding media bus code
+ * @fmt_reg: S5PCSIS_CONFIG register value
+ * @data_alignment: MIPI-CSI data alignment in bits
+ */
+struct csis_pix_format {
+ unsigned int pix_width_alignment;
+ u32 code;
+ u32 fmt_reg;
+ u8 data_alignment;
+};
+
+static const struct csis_pix_format s5pcsis_formats[] = {
+ {
+ .code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .fmt_reg = S5PCSIS_CFG_FMT_YCBCR422_8BIT,
+ .data_alignment = 32,
+ }, {
+ .code = MEDIA_BUS_FMT_JPEG_1X8,
+ .fmt_reg = S5PCSIS_CFG_FMT_USER(1),
+ .data_alignment = 32,
+ }, {
+ .code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8,
+ .fmt_reg = S5PCSIS_CFG_FMT_USER(1),
+ .data_alignment = 32,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .fmt_reg = S5PCSIS_CFG_FMT_RAW8,
+ .data_alignment = 24,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .fmt_reg = S5PCSIS_CFG_FMT_RAW10,
+ .data_alignment = 24,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .fmt_reg = S5PCSIS_CFG_FMT_RAW12,
+ .data_alignment = 24,
+ }
+};
+
+#define s5pcsis_write(__csis, __r, __v) writel(__v, __csis->regs + __r)
+#define s5pcsis_read(__csis, __r) readl(__csis->regs + __r)
+
+static struct csis_state *sd_to_csis_state(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct csis_state, sd);
+}
+
+static const struct csis_pix_format *find_csis_format(
+ struct v4l2_mbus_framefmt *mf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s5pcsis_formats); i++)
+ if (mf->code == s5pcsis_formats[i].code)
+ return &s5pcsis_formats[i];
+ return NULL;
+}
+
+static void s5pcsis_enable_interrupts(struct csis_state *state, bool on)
+{
+ u32 val = s5pcsis_read(state, S5PCSIS_INTMSK);
+ if (on)
+ val |= state->interrupt_mask;
+ else
+ val &= ~state->interrupt_mask;
+ s5pcsis_write(state, S5PCSIS_INTMSK, val);
+}
+
+static void s5pcsis_reset(struct csis_state *state)
+{
+ u32 val = s5pcsis_read(state, S5PCSIS_CTRL);
+
+ s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_RESET);
+ udelay(10);
+}
+
+static void s5pcsis_system_enable(struct csis_state *state, int on)
+{
+ u32 val, mask;
+
+ val = s5pcsis_read(state, S5PCSIS_CTRL);
+ if (on)
+ val |= S5PCSIS_CTRL_ENABLE;
+ else
+ val &= ~S5PCSIS_CTRL_ENABLE;
+ s5pcsis_write(state, S5PCSIS_CTRL, val);
+
+ val = s5pcsis_read(state, S5PCSIS_DPHYCTRL);
+ val &= ~S5PCSIS_DPHYCTRL_ENABLE;
+ if (on) {
+ mask = (1 << (state->num_lanes + 1)) - 1;
+ val |= (mask & S5PCSIS_DPHYCTRL_ENABLE);
+ }
+ s5pcsis_write(state, S5PCSIS_DPHYCTRL, val);
+}
+
+/* Called with the state.lock mutex held */
+static void __s5pcsis_set_format(struct csis_state *state)
+{
+ struct v4l2_mbus_framefmt *mf = &state->format;
+ u32 val;
+
+ v4l2_dbg(1, debug, &state->sd, "fmt: %#x, %d x %d\n",
+ mf->code, mf->width, mf->height);
+
+ /* Color format */
+ val = s5pcsis_read(state, S5PCSIS_CONFIG);
+ val = (val & ~S5PCSIS_CFG_FMT_MASK) | state->csis_fmt->fmt_reg;
+ s5pcsis_write(state, S5PCSIS_CONFIG, val);
+
+ /* Pixel resolution */
+ val = (mf->width << 16) | mf->height;
+ s5pcsis_write(state, S5PCSIS_RESOL, val);
+}
+
+static void s5pcsis_set_hsync_settle(struct csis_state *state, int settle)
+{
+ u32 val = s5pcsis_read(state, S5PCSIS_DPHYCTRL);
+
+ val = (val & ~S5PCSIS_DPHYCTRL_HSS_MASK) | (settle << 27);
+ s5pcsis_write(state, S5PCSIS_DPHYCTRL, val);
+}
+
+static void s5pcsis_set_params(struct csis_state *state)
+{
+ u32 val;
+
+ val = s5pcsis_read(state, S5PCSIS_CONFIG);
+ val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (state->num_lanes - 1);
+ s5pcsis_write(state, S5PCSIS_CONFIG, val);
+
+ __s5pcsis_set_format(state);
+ s5pcsis_set_hsync_settle(state, state->hs_settle);
+
+ val = s5pcsis_read(state, S5PCSIS_CTRL);
+ if (state->csis_fmt->data_alignment == 32)
+ val |= S5PCSIS_CTRL_ALIGN_32BIT;
+ else /* 24-bits */
+ val &= ~S5PCSIS_CTRL_ALIGN_32BIT;
+
+ val &= ~S5PCSIS_CTRL_WCLK_EXTCLK;
+ if (state->wclk_ext)
+ val |= S5PCSIS_CTRL_WCLK_EXTCLK;
+ s5pcsis_write(state, S5PCSIS_CTRL, val);
+
+ /* Update the shadow register. */
+ val = s5pcsis_read(state, S5PCSIS_CTRL);
+ s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_UPDATE_SHADOW);
+}
+
+static void s5pcsis_clk_put(struct csis_state *state)
+{
+ int i;
+
+ for (i = 0; i < NUM_CSIS_CLOCKS; i++) {
+ if (IS_ERR(state->clock[i]))
+ continue;
+ clk_unprepare(state->clock[i]);
+ clk_put(state->clock[i]);
+ state->clock[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int s5pcsis_clk_get(struct csis_state *state)
+{
+ struct device *dev = &state->pdev->dev;
+ int i, ret;
+
+ for (i = 0; i < NUM_CSIS_CLOCKS; i++)
+ state->clock[i] = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < NUM_CSIS_CLOCKS; i++) {
+ state->clock[i] = clk_get(dev, csi_clock_name[i]);
+ if (IS_ERR(state->clock[i])) {
+ ret = PTR_ERR(state->clock[i]);
+ goto err;
+ }
+ ret = clk_prepare(state->clock[i]);
+ if (ret < 0) {
+ clk_put(state->clock[i]);
+ state->clock[i] = ERR_PTR(-EINVAL);
+ goto err;
+ }
+ }
+ return 0;
+err:
+ s5pcsis_clk_put(state);
+ dev_err(dev, "failed to get clock: %s\n", csi_clock_name[i]);
+ return ret;
+}
+
+static void dump_regs(struct csis_state *state, const char *label)
+{
+ struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ { 0x00, "CTRL" },
+ { 0x04, "DPHYCTRL" },
+ { 0x08, "CONFIG" },
+ { 0x0c, "DPHYSTS" },
+ { 0x10, "INTMSK" },
+ { 0x2c, "RESOL" },
+ { 0x38, "SDW_CONFIG" },
+ };
+ u32 i;
+
+ v4l2_info(&state->sd, "--- %s ---\n", label);
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 cfg = s5pcsis_read(state, registers[i].offset);
+ v4l2_info(&state->sd, "%10s: 0x%08x\n", registers[i].name, cfg);
+ }
+}
+
+static void s5pcsis_start_stream(struct csis_state *state)
+{
+ s5pcsis_reset(state);
+ s5pcsis_set_params(state);
+ s5pcsis_system_enable(state, true);
+ s5pcsis_enable_interrupts(state, true);
+}
+
+static void s5pcsis_stop_stream(struct csis_state *state)
+{
+ s5pcsis_enable_interrupts(state, false);
+ s5pcsis_system_enable(state, false);
+}
+
+static void s5pcsis_clear_counters(struct csis_state *state)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&state->slock, flags);
+ for (i = 0; i < S5PCSIS_NUM_EVENTS; i++)
+ state->events[i].counter = 0;
+ spin_unlock_irqrestore(&state->slock, flags);
+}
+
+static void s5pcsis_log_counters(struct csis_state *state, bool non_errors)
+{
+ int i = non_errors ? S5PCSIS_NUM_EVENTS : S5PCSIS_NUM_EVENTS - 4;
+ unsigned long flags;
+
+ spin_lock_irqsave(&state->slock, flags);
+
+ for (i--; i >= 0; i--) {
+ if (state->events[i].counter > 0 || debug)
+ v4l2_info(&state->sd, "%s events: %d\n",
+ state->events[i].name,
+ state->events[i].counter);
+ }
+ spin_unlock_irqrestore(&state->slock, flags);
+}
+
+/*
+ * V4L2 subdev operations
+ */
+static int s5pcsis_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+ struct device *dev = &state->pdev->dev;
+
+ if (on)
+ return pm_runtime_resume_and_get(dev);
+
+ return pm_runtime_put_sync(dev);
+}
+
+static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+ int ret = 0;
+
+ v4l2_dbg(1, debug, sd, "%s: %d, state: 0x%x\n",
+ __func__, enable, state->flags);
+
+ if (enable) {
+ s5pcsis_clear_counters(state);
+ ret = pm_runtime_resume_and_get(&state->pdev->dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ mutex_lock(&state->lock);
+ if (enable) {
+ if (state->flags & ST_SUSPENDED) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+ s5pcsis_start_stream(state);
+ state->flags |= ST_STREAMING;
+ } else {
+ s5pcsis_stop_stream(state);
+ state->flags &= ~ST_STREAMING;
+ if (debug > 0)
+ s5pcsis_log_counters(state, true);
+ }
+unlock:
+ mutex_unlock(&state->lock);
+ if (!enable)
+ pm_runtime_put(&state->pdev->dev);
+
+ return ret;
+}
+
+static int s5pcsis_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(s5pcsis_formats))
+ return -EINVAL;
+
+ code->code = s5pcsis_formats[code->index].code;
+ return 0;
+}
+
+static struct csis_pix_format const *s5pcsis_try_format(
+ struct v4l2_mbus_framefmt *mf)
+{
+ struct csis_pix_format const *csis_fmt;
+
+ csis_fmt = find_csis_format(mf);
+ if (csis_fmt == NULL)
+ csis_fmt = &s5pcsis_formats[0];
+
+ mf->code = csis_fmt->code;
+ v4l_bound_align_image(&mf->width, 1, CSIS_MAX_PIX_WIDTH,
+ csis_fmt->pix_width_alignment,
+ &mf->height, 1, CSIS_MAX_PIX_HEIGHT, 1,
+ 0);
+ return csis_fmt;
+}
+
+static struct v4l2_mbus_framefmt *__s5pcsis_get_format(
+ struct csis_state *state, struct v4l2_subdev_state *sd_state,
+ enum v4l2_subdev_format_whence which)
+{
+ if (which == V4L2_SUBDEV_FORMAT_TRY)
+ return sd_state ? v4l2_subdev_get_try_format(&state->sd,
+ sd_state, 0) : NULL;
+
+ return &state->format;
+}
+
+static int s5pcsis_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+ struct csis_pix_format const *csis_fmt;
+ struct v4l2_mbus_framefmt *mf;
+
+ mf = __s5pcsis_get_format(state, sd_state, fmt->which);
+
+ if (fmt->pad == CSIS_PAD_SOURCE) {
+ if (mf) {
+ mutex_lock(&state->lock);
+ fmt->format = *mf;
+ mutex_unlock(&state->lock);
+ }
+ return 0;
+ }
+ csis_fmt = s5pcsis_try_format(&fmt->format);
+ if (mf) {
+ mutex_lock(&state->lock);
+ *mf = fmt->format;
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ state->csis_fmt = csis_fmt;
+ mutex_unlock(&state->lock);
+ }
+ return 0;
+}
+
+static int s5pcsis_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+ struct v4l2_mbus_framefmt *mf;
+
+ mf = __s5pcsis_get_format(state, sd_state, fmt->which);
+ if (!mf)
+ return -EINVAL;
+
+ mutex_lock(&state->lock);
+ fmt->format = *mf;
+ mutex_unlock(&state->lock);
+ return 0;
+}
+
+static int s5pcsis_s_rx_buffer(struct v4l2_subdev *sd, void *buf,
+ unsigned int *size)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+ unsigned long flags;
+
+ *size = min_t(unsigned int, *size, S5PCSIS_PKTDATA_SIZE);
+
+ spin_lock_irqsave(&state->slock, flags);
+ state->pkt_buf.data = buf;
+ state->pkt_buf.len = *size;
+ spin_unlock_irqrestore(&state->slock, flags);
+
+ return 0;
+}
+
+static int s5pcsis_log_status(struct v4l2_subdev *sd)
+{
+ struct csis_state *state = sd_to_csis_state(sd);
+
+ mutex_lock(&state->lock);
+ s5pcsis_log_counters(state, true);
+ if (debug && (state->flags & ST_POWERED))
+ dump_regs(state, __func__);
+ mutex_unlock(&state->lock);
+ return 0;
+}
+
+static const struct v4l2_subdev_core_ops s5pcsis_core_ops = {
+ .s_power = s5pcsis_s_power,
+ .log_status = s5pcsis_log_status,
+};
+
+static const struct v4l2_subdev_pad_ops s5pcsis_pad_ops = {
+ .enum_mbus_code = s5pcsis_enum_mbus_code,
+ .get_fmt = s5pcsis_get_fmt,
+ .set_fmt = s5pcsis_set_fmt,
+};
+
+static const struct v4l2_subdev_video_ops s5pcsis_video_ops = {
+ .s_rx_buffer = s5pcsis_s_rx_buffer,
+ .s_stream = s5pcsis_s_stream,
+};
+
+static const struct v4l2_subdev_ops s5pcsis_subdev_ops = {
+ .core = &s5pcsis_core_ops,
+ .pad = &s5pcsis_pad_ops,
+ .video = &s5pcsis_video_ops,
+};
+
+static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id)
+{
+ struct csis_state *state = dev_id;
+ struct csis_pktbuf *pktbuf = &state->pkt_buf;
+ unsigned long flags;
+ u32 status;
+
+ status = s5pcsis_read(state, S5PCSIS_INTSRC);
+ spin_lock_irqsave(&state->slock, flags);
+
+ if ((status & S5PCSIS_INTSRC_NON_IMAGE_DATA) && pktbuf->data) {
+ u32 offset;
+
+ if (status & S5PCSIS_INTSRC_EVEN)
+ offset = S5PCSIS_PKTDATA_EVEN;
+ else
+ offset = S5PCSIS_PKTDATA_ODD;
+
+ memcpy(pktbuf->data, (u8 __force *)state->regs + offset,
+ pktbuf->len);
+ pktbuf->data = NULL;
+ rmb();
+ }
+
+ /* Update the event/error counters */
+ if ((status & S5PCSIS_INTSRC_ERRORS) || debug) {
+ int i;
+ for (i = 0; i < S5PCSIS_NUM_EVENTS; i++) {
+ if (!(status & state->events[i].mask))
+ continue;
+ state->events[i].counter++;
+ v4l2_dbg(2, debug, &state->sd, "%s: %d\n",
+ state->events[i].name,
+ state->events[i].counter);
+ }
+ v4l2_dbg(2, debug, &state->sd, "status: %08x\n", status);
+ }
+ spin_unlock_irqrestore(&state->slock, flags);
+
+ s5pcsis_write(state, S5PCSIS_INTSRC, status);
+ return IRQ_HANDLED;
+}
+
+static int s5pcsis_parse_dt(struct platform_device *pdev,
+ struct csis_state *state)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 };
+ int ret;
+
+ if (of_property_read_u32(node, "clock-frequency",
+ &state->clk_frequency))
+ state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ;
+ if (of_property_read_u32(node, "bus-width",
+ &state->max_num_lanes))
+ return -EINVAL;
+
+ node = of_graph_get_next_endpoint(node, NULL);
+ if (!node) {
+ dev_err(&pdev->dev, "No port node at %pOF\n",
+ pdev->dev.of_node);
+ return -EINVAL;
+ }
+ /* Get port node and validate MIPI-CSI channel id. */
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint);
+ if (ret)
+ goto err;
+
+ state->index = endpoint.base.port - FIMC_INPUT_MIPI_CSI2_0;
+ if (state->index >= CSIS_MAX_ENTITIES) {
+ ret = -ENXIO;
+ goto err;
+ }
+
+ /* Get MIPI CSI-2 bus configuration from the endpoint node. */
+ of_property_read_u32(node, "samsung,csis-hs-settle",
+ &state->hs_settle);
+ state->wclk_ext = of_property_read_bool(node,
+ "samsung,csis-wclk");
+
+ state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes;
+
+err:
+ of_node_put(node);
+ return ret;
+}
+
+static int s5pcsis_pm_resume(struct device *dev, bool runtime);
+static const struct of_device_id s5pcsis_of_match[];
+
+static int s5pcsis_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ const struct csis_drvdata *drv_data;
+ struct device *dev = &pdev->dev;
+ struct csis_state *state;
+ int ret = -ENOMEM;
+ int i;
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ mutex_init(&state->lock);
+ spin_lock_init(&state->slock);
+ state->pdev = pdev;
+
+ of_id = of_match_node(s5pcsis_of_match, dev->of_node);
+ if (WARN_ON(of_id == NULL))
+ return -EINVAL;
+
+ drv_data = of_id->data;
+ state->interrupt_mask = drv_data->interrupt_mask;
+
+ ret = s5pcsis_parse_dt(pdev, state);
+ if (ret < 0)
+ return ret;
+
+ if (state->num_lanes == 0 || state->num_lanes > state->max_num_lanes) {
+ dev_err(dev, "Unsupported number of data lanes: %d (max. %d)\n",
+ state->num_lanes, state->max_num_lanes);
+ return -EINVAL;
+ }
+
+ state->phy = devm_phy_get(dev, "csis");
+ if (IS_ERR(state->phy))
+ return PTR_ERR(state->phy);
+
+ state->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(state->regs))
+ return PTR_ERR(state->regs);
+
+ state->irq = platform_get_irq(pdev, 0);
+ if (state->irq < 0)
+ return state->irq;
+
+ for (i = 0; i < CSIS_NUM_SUPPLIES; i++)
+ state->supplies[i].supply = csis_supply_name[i];
+
+ ret = devm_regulator_bulk_get(dev, CSIS_NUM_SUPPLIES,
+ state->supplies);
+ if (ret)
+ return ret;
+
+ ret = s5pcsis_clk_get(state);
+ if (ret < 0)
+ return ret;
+
+ if (state->clk_frequency)
+ ret = clk_set_rate(state->clock[CSIS_CLK_MUX],
+ state->clk_frequency);
+ else
+ dev_WARN(dev, "No clock frequency specified!\n");
+ if (ret < 0)
+ goto e_clkput;
+
+ ret = clk_enable(state->clock[CSIS_CLK_MUX]);
+ if (ret < 0)
+ goto e_clkput;
+
+ ret = devm_request_irq(dev, state->irq, s5pcsis_irq_handler,
+ 0, dev_name(dev), state);
+ if (ret) {
+ dev_err(dev, "Interrupt request failed\n");
+ goto e_clkdis;
+ }
+
+ v4l2_subdev_init(&state->sd, &s5pcsis_subdev_ops);
+ state->sd.owner = THIS_MODULE;
+ snprintf(state->sd.name, sizeof(state->sd.name), "%s.%d",
+ CSIS_SUBDEV_NAME, state->index);
+ state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ state->csis_fmt = &s5pcsis_formats[0];
+
+ state->format.code = s5pcsis_formats[0].code;
+ state->format.width = S5PCSIS_DEF_PIX_WIDTH;
+ state->format.height = S5PCSIS_DEF_PIX_HEIGHT;
+
+ state->sd.entity.function = MEDIA_ENT_F_IO_V4L;
+ state->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ state->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&state->sd.entity,
+ CSIS_PADS_NUM, state->pads);
+ if (ret < 0)
+ goto e_clkdis;
+
+ /* This allows to retrieve the platform device id by the host driver */
+ v4l2_set_subdevdata(&state->sd, pdev);
+
+ /* .. and a pointer to the subdev. */
+ platform_set_drvdata(pdev, &state->sd);
+ memcpy(state->events, s5pcsis_events, sizeof(state->events));
+
+ pm_runtime_enable(dev);
+ if (!pm_runtime_enabled(dev)) {
+ ret = s5pcsis_pm_resume(dev, true);
+ if (ret < 0)
+ goto e_m_ent;
+ }
+
+ dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n",
+ state->num_lanes, state->hs_settle, state->wclk_ext,
+ state->clk_frequency);
+ return 0;
+
+e_m_ent:
+ media_entity_cleanup(&state->sd.entity);
+e_clkdis:
+ clk_disable(state->clock[CSIS_CLK_MUX]);
+e_clkput:
+ s5pcsis_clk_put(state);
+ return ret;
+}
+
+static int s5pcsis_pm_suspend(struct device *dev, bool runtime)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct csis_state *state = sd_to_csis_state(sd);
+ int ret = 0;
+
+ v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n",
+ __func__, state->flags);
+
+ mutex_lock(&state->lock);
+ if (state->flags & ST_POWERED) {
+ s5pcsis_stop_stream(state);
+ ret = phy_power_off(state->phy);
+ if (ret)
+ goto unlock;
+ ret = regulator_bulk_disable(CSIS_NUM_SUPPLIES,
+ state->supplies);
+ if (ret)
+ goto unlock;
+ clk_disable(state->clock[CSIS_CLK_GATE]);
+ state->flags &= ~ST_POWERED;
+ if (!runtime)
+ state->flags |= ST_SUSPENDED;
+ }
+ unlock:
+ mutex_unlock(&state->lock);
+ return ret ? -EAGAIN : 0;
+}
+
+static int s5pcsis_pm_resume(struct device *dev, bool runtime)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct csis_state *state = sd_to_csis_state(sd);
+ int ret = 0;
+
+ v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n",
+ __func__, state->flags);
+
+ mutex_lock(&state->lock);
+ if (!runtime && !(state->flags & ST_SUSPENDED))
+ goto unlock;
+
+ if (!(state->flags & ST_POWERED)) {
+ ret = regulator_bulk_enable(CSIS_NUM_SUPPLIES,
+ state->supplies);
+ if (ret)
+ goto unlock;
+ ret = phy_power_on(state->phy);
+ if (!ret) {
+ state->flags |= ST_POWERED;
+ } else {
+ regulator_bulk_disable(CSIS_NUM_SUPPLIES,
+ state->supplies);
+ goto unlock;
+ }
+ clk_enable(state->clock[CSIS_CLK_GATE]);
+ }
+ if (state->flags & ST_STREAMING)
+ s5pcsis_start_stream(state);
+
+ state->flags &= ~ST_SUSPENDED;
+ unlock:
+ mutex_unlock(&state->lock);
+ return ret ? -EAGAIN : 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int s5pcsis_suspend(struct device *dev)
+{
+ return s5pcsis_pm_suspend(dev, false);
+}
+
+static int s5pcsis_resume(struct device *dev)
+{
+ return s5pcsis_pm_resume(dev, false);
+}
+#endif
+
+#ifdef CONFIG_PM
+static int s5pcsis_runtime_suspend(struct device *dev)
+{
+ return s5pcsis_pm_suspend(dev, true);
+}
+
+static int s5pcsis_runtime_resume(struct device *dev)
+{
+ return s5pcsis_pm_resume(dev, true);
+}
+#endif
+
+static void s5pcsis_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct csis_state *state = sd_to_csis_state(sd);
+
+ pm_runtime_disable(&pdev->dev);
+ s5pcsis_pm_suspend(&pdev->dev, true);
+ clk_disable(state->clock[CSIS_CLK_MUX]);
+ pm_runtime_set_suspended(&pdev->dev);
+ s5pcsis_clk_put(state);
+
+ media_entity_cleanup(&state->sd.entity);
+}
+
+static const struct dev_pm_ops s5pcsis_pm_ops = {
+ SET_RUNTIME_PM_OPS(s5pcsis_runtime_suspend, s5pcsis_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(s5pcsis_suspend, s5pcsis_resume)
+};
+
+static const struct csis_drvdata exynos4_csis_drvdata = {
+ .interrupt_mask = S5PCSIS_INTMSK_EXYNOS4_EN_ALL,
+};
+
+static const struct csis_drvdata exynos5_csis_drvdata = {
+ .interrupt_mask = S5PCSIS_INTMSK_EXYNOS5_EN_ALL,
+};
+
+static const struct of_device_id s5pcsis_of_match[] = {
+ {
+ .compatible = "samsung,s5pv210-csis",
+ .data = &exynos4_csis_drvdata,
+ }, {
+ .compatible = "samsung,exynos4210-csis",
+ .data = &exynos4_csis_drvdata,
+ }, {
+ .compatible = "samsung,exynos5250-csis",
+ .data = &exynos5_csis_drvdata,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, s5pcsis_of_match);
+
+static struct platform_driver s5pcsis_driver = {
+ .probe = s5pcsis_probe,
+ .remove_new = s5pcsis_remove,
+ .driver = {
+ .of_match_table = s5pcsis_of_match,
+ .name = CSIS_DRIVER_NAME,
+ .pm = &s5pcsis_pm_ops,
+ },
+};
+
+module_platform_driver(s5pcsis_driver);
+
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI-CSI2 receiver driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/exynos4-is/mipi-csis.h b/drivers/media/platform/samsung/exynos4-is/mipi-csis.h
new file mode 100644
index 0000000000..193f253c79
--- /dev/null
+++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Samsung S5P/EXYNOS4 SoC series MIPI-CSI receiver driver
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ */
+#ifndef S5P_MIPI_CSIS_H_
+#define S5P_MIPI_CSIS_H_
+
+#define CSIS_DRIVER_NAME "s5p-mipi-csis"
+#define CSIS_SUBDEV_NAME CSIS_DRIVER_NAME
+#define CSIS_MAX_ENTITIES 2
+#define CSIS0_MAX_LANES 4
+#define CSIS1_MAX_LANES 2
+
+#define CSIS_PAD_SINK 0
+#define CSIS_PAD_SOURCE 1
+#define CSIS_PADS_NUM 2
+
+#define S5PCSIS_DEF_PIX_WIDTH 640
+#define S5PCSIS_DEF_PIX_HEIGHT 480
+
+#endif
diff --git a/drivers/media/platform/samsung/s3c-camif/Kconfig b/drivers/media/platform/samsung/s3c-camif/Kconfig
new file mode 100644
index 0000000000..f359f6382f
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_S3C_CAMIF
+ tristate "Samsung 3C64XX SoC Camera Interface driver"
+ depends on V4L_PLATFORM_DRIVERS
+ depends on VIDEO_DEV && I2C && PM
+ depends on ARCH_S3C64XX || COMPILE_TEST
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select VIDEOBUF2_DMA_CONTIG
+ help
+ This is a v4l2 driver for s3c64xx SoC series camera host interface
+ (CAMIF).
+
+ To compile this driver as a module, choose M here: the module
+ will be called s3c-camif.
diff --git a/drivers/media/platform/samsung/s3c-camif/Makefile b/drivers/media/platform/samsung/s3c-camif/Makefile
new file mode 100644
index 0000000000..70ee042a3d
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Makefile for s3c244x/s3c64xx CAMIF driver
+
+s3c-camif-objs := camif-core.o camif-capture.o camif-regs.o
+
+obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif.o
diff --git a/drivers/media/platform/samsung/s3c-camif/camif-capture.c b/drivers/media/platform/samsung/s3c-camif/camif-capture.c
new file mode 100644
index 0000000000..0f5b3845d7
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/camif-capture.c
@@ -0,0 +1,1651 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver
+ *
+ * Copyright (C) 2012 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
+ * Copyright (C) 2012 Tomasz Figa <tomasz.figa@gmail.com>
+ *
+ * Based on drivers/media/platform/s5p-fimc,
+ * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd.
+*/
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "camif-core.h"
+#include "camif-regs.h"
+
+static int debug;
+module_param(debug, int, 0644);
+
+/* Locking: called with vp->camif->slock spinlock held */
+static void camif_cfg_video_path(struct camif_vp *vp)
+{
+ WARN_ON(s3c_camif_get_scaler_config(vp, &vp->scaler));
+ camif_hw_set_scaler(vp);
+ camif_hw_set_flip(vp);
+ camif_hw_set_target_format(vp);
+ camif_hw_set_output_dma(vp);
+}
+
+static void camif_prepare_dma_offset(struct camif_vp *vp)
+{
+ struct camif_frame *f = &vp->out_frame;
+
+ f->dma_offset.initial = f->rect.top * f->f_width + f->rect.left;
+ f->dma_offset.line = f->f_width - (f->rect.left + f->rect.width);
+
+ pr_debug("dma_offset: initial: %d, line: %d\n",
+ f->dma_offset.initial, f->dma_offset.line);
+}
+
+/* Locking: called with camif->slock spinlock held */
+static int s3c_camif_hw_init(struct camif_dev *camif, struct camif_vp *vp)
+{
+ const struct s3c_camif_variant *variant = camif->variant;
+
+ if (camif->sensor.sd == NULL || vp->out_fmt == NULL)
+ return -EINVAL;
+
+ if (variant->ip_revision == S3C244X_CAMIF_IP_REV)
+ camif_hw_clear_fifo_overflow(vp);
+ camif_hw_set_camera_bus(camif);
+ camif_hw_set_source_format(camif);
+ camif_hw_set_camera_crop(camif);
+ camif_hw_set_test_pattern(camif, camif->test_pattern);
+ if (variant->has_img_effect)
+ camif_hw_set_effect(camif, camif->colorfx,
+ camif->colorfx_cr, camif->colorfx_cb);
+ if (variant->ip_revision == S3C6410_CAMIF_IP_REV)
+ camif_hw_set_input_path(vp);
+ camif_cfg_video_path(vp);
+ vp->state &= ~ST_VP_CONFIG;
+
+ return 0;
+}
+
+/*
+ * Initialize the video path, only up from the scaler stage. The camera
+ * input interface set up is skipped. This is useful to enable one of the
+ * video paths when the other is already running.
+ * Locking: called with camif->slock spinlock held.
+ */
+static int s3c_camif_hw_vp_init(struct camif_dev *camif, struct camif_vp *vp)
+{
+ unsigned int ip_rev = camif->variant->ip_revision;
+
+ if (vp->out_fmt == NULL)
+ return -EINVAL;
+
+ camif_prepare_dma_offset(vp);
+ if (ip_rev == S3C244X_CAMIF_IP_REV)
+ camif_hw_clear_fifo_overflow(vp);
+ camif_cfg_video_path(vp);
+ vp->state &= ~ST_VP_CONFIG;
+ return 0;
+}
+
+static int sensor_set_power(struct camif_dev *camif, int on)
+{
+ struct cam_sensor *sensor = &camif->sensor;
+ int err = 0;
+
+ if (camif->sensor.power_count == !on)
+ err = v4l2_subdev_call(sensor->sd, core, s_power, on);
+ if (err == -ENOIOCTLCMD)
+ err = 0;
+ if (!err)
+ sensor->power_count += on ? 1 : -1;
+
+ pr_debug("on: %d, power_count: %d, err: %d\n",
+ on, sensor->power_count, err);
+
+ return err;
+}
+
+static int sensor_set_streaming(struct camif_dev *camif, int on)
+{
+ struct cam_sensor *sensor = &camif->sensor;
+ int err = 0;
+
+ if (camif->sensor.stream_count == !on)
+ err = v4l2_subdev_call(sensor->sd, video, s_stream, on);
+ if (!err)
+ sensor->stream_count += on ? 1 : -1;
+
+ pr_debug("on: %d, stream_count: %d, err: %d\n",
+ on, sensor->stream_count, err);
+
+ return err;
+}
+
+/*
+ * Reinitialize the driver so it is ready to start streaming again.
+ * Return any buffers to vb2, perform CAMIF software reset and
+ * turn off streaming at the data pipeline (sensor) if required.
+ */
+static int camif_reinitialize(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_buffer *buf;
+ unsigned long flags;
+ bool streaming;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ streaming = vp->state & ST_VP_SENSOR_STREAMING;
+
+ vp->state &= ~(ST_VP_PENDING | ST_VP_RUNNING | ST_VP_OFF |
+ ST_VP_ABORTING | ST_VP_STREAMING |
+ ST_VP_SENSOR_STREAMING | ST_VP_LASTIRQ);
+
+ /* Release unused buffers */
+ while (!list_empty(&vp->pending_buf_q)) {
+ buf = camif_pending_queue_pop(vp);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&vp->active_buf_q)) {
+ buf = camif_active_queue_pop(vp);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ if (!streaming)
+ return 0;
+
+ return sensor_set_streaming(camif, 0);
+}
+
+static bool s3c_vp_active(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ unsigned long flags;
+ bool ret;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ ret = (vp->state & ST_VP_RUNNING) || (vp->state & ST_VP_PENDING);
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ return ret;
+}
+
+static bool camif_is_streaming(struct camif_dev *camif)
+{
+ unsigned long flags;
+ bool status;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ status = camif->stream_count > 0;
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ return status;
+}
+
+static int camif_stop_capture(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ unsigned long flags;
+ int ret;
+
+ if (!s3c_vp_active(vp))
+ return 0;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ vp->state &= ~(ST_VP_OFF | ST_VP_LASTIRQ);
+ vp->state |= ST_VP_ABORTING;
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ ret = wait_event_timeout(vp->irq_queue,
+ !(vp->state & ST_VP_ABORTING),
+ msecs_to_jiffies(CAMIF_STOP_TIMEOUT));
+
+ spin_lock_irqsave(&camif->slock, flags);
+
+ if (ret == 0 && !(vp->state & ST_VP_OFF)) {
+ /* Timed out, forcibly stop capture */
+ vp->state &= ~(ST_VP_OFF | ST_VP_ABORTING |
+ ST_VP_LASTIRQ);
+
+ camif_hw_disable_capture(vp);
+ camif_hw_enable_scaler(vp, false);
+ }
+
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ return camif_reinitialize(vp);
+}
+
+static int camif_prepare_addr(struct camif_vp *vp, struct vb2_buffer *vb,
+ struct camif_addr *paddr)
+{
+ struct camif_frame *frame = &vp->out_frame;
+ u32 pix_size;
+
+ if (vb == NULL || frame == NULL)
+ return -EINVAL;
+
+ pix_size = frame->rect.width * frame->rect.height;
+
+ pr_debug("colplanes: %d, pix_size: %u\n",
+ vp->out_fmt->colplanes, pix_size);
+
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ switch (vp->out_fmt->colplanes) {
+ case 1:
+ paddr->cb = 0;
+ paddr->cr = 0;
+ break;
+ case 2:
+ /* decompose Y into Y/Cb */
+ paddr->cb = (u32)(paddr->y + pix_size);
+ paddr->cr = 0;
+ break;
+ case 3:
+ paddr->cb = (u32)(paddr->y + pix_size);
+ /* decompose Y into Y/Cb/Cr */
+ if (vp->out_fmt->color == IMG_FMT_YCBCR422P)
+ paddr->cr = (u32)(paddr->cb + (pix_size >> 1));
+ else /* 420 */
+ paddr->cr = (u32)(paddr->cb + (pix_size >> 2));
+
+ if (vp->out_fmt->color == IMG_FMT_YCRCB420)
+ swap(paddr->cb, paddr->cr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pr_debug("DMA address: y: %pad cb: %pad cr: %pad\n",
+ &paddr->y, &paddr->cb, &paddr->cr);
+
+ return 0;
+}
+
+irqreturn_t s3c_camif_irq_handler(int irq, void *priv)
+{
+ struct camif_vp *vp = priv;
+ struct camif_dev *camif = vp->camif;
+ unsigned int ip_rev = camif->variant->ip_revision;
+ unsigned int status;
+
+ spin_lock(&camif->slock);
+
+ if (ip_rev == S3C6410_CAMIF_IP_REV)
+ camif_hw_clear_pending_irq(vp);
+
+ status = camif_hw_get_status(vp);
+
+ if (ip_rev == S3C244X_CAMIF_IP_REV && (status & CISTATUS_OVF_MASK)) {
+ camif_hw_clear_fifo_overflow(vp);
+ goto unlock;
+ }
+
+ if (vp->state & ST_VP_ABORTING) {
+ if (vp->state & ST_VP_OFF) {
+ /* Last IRQ */
+ vp->state &= ~(ST_VP_OFF | ST_VP_ABORTING |
+ ST_VP_LASTIRQ);
+ wake_up(&vp->irq_queue);
+ goto unlock;
+ } else if (vp->state & ST_VP_LASTIRQ) {
+ camif_hw_disable_capture(vp);
+ camif_hw_enable_scaler(vp, false);
+ camif_hw_set_lastirq(vp, false);
+ vp->state |= ST_VP_OFF;
+ } else {
+ /* Disable capture, enable last IRQ */
+ camif_hw_set_lastirq(vp, true);
+ vp->state |= ST_VP_LASTIRQ;
+ }
+ }
+
+ if (!list_empty(&vp->pending_buf_q) && (vp->state & ST_VP_RUNNING) &&
+ !list_empty(&vp->active_buf_q)) {
+ unsigned int index;
+ struct camif_buffer *vbuf;
+ /*
+ * Get previous DMA write buffer index:
+ * 0 => DMA buffer 0, 2;
+ * 1 => DMA buffer 1, 3.
+ */
+ index = (CISTATUS_FRAMECNT(status) + 2) & 1;
+ vbuf = camif_active_queue_peek(vp, index);
+
+ if (!WARN_ON(vbuf == NULL)) {
+ /* Dequeue a filled buffer */
+ vbuf->vb.vb2_buf.timestamp = ktime_get_ns();
+ vbuf->vb.sequence = vp->frame_sequence++;
+ vb2_buffer_done(&vbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+ /* Set up an empty buffer at the DMA engine */
+ vbuf = camif_pending_queue_pop(vp);
+ vbuf->index = index;
+ camif_hw_set_output_addr(vp, &vbuf->paddr, index);
+ camif_hw_set_output_addr(vp, &vbuf->paddr, index + 2);
+
+ /* Scheduled in H/W, add to the queue */
+ camif_active_queue_add(vp, vbuf);
+ }
+ } else if (!(vp->state & ST_VP_ABORTING) &&
+ (vp->state & ST_VP_PENDING)) {
+ vp->state |= ST_VP_RUNNING;
+ }
+
+ if (vp->state & ST_VP_CONFIG) {
+ camif_prepare_dma_offset(vp);
+ camif_hw_set_camera_crop(camif);
+ camif_hw_set_scaler(vp);
+ camif_hw_set_flip(vp);
+ camif_hw_set_test_pattern(camif, camif->test_pattern);
+ if (camif->variant->has_img_effect)
+ camif_hw_set_effect(camif, camif->colorfx,
+ camif->colorfx_cr, camif->colorfx_cb);
+ vp->state &= ~ST_VP_CONFIG;
+ }
+unlock:
+ spin_unlock(&camif->slock);
+ return IRQ_HANDLED;
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct camif_vp *vp = vb2_get_drv_priv(vq);
+ struct camif_dev *camif = vp->camif;
+ unsigned long flags;
+ int ret;
+
+ /*
+ * We assume the codec capture path is always activated
+ * first, before the preview path starts streaming.
+ * This is required to avoid internal FIFO overflow and
+ * a need for CAMIF software reset.
+ */
+ spin_lock_irqsave(&camif->slock, flags);
+
+ if (camif->stream_count == 0) {
+ camif_hw_reset(camif);
+ ret = s3c_camif_hw_init(camif, vp);
+ } else {
+ ret = s3c_camif_hw_vp_init(camif, vp);
+ }
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ if (ret < 0) {
+ camif_reinitialize(vp);
+ return ret;
+ }
+
+ spin_lock_irqsave(&camif->slock, flags);
+ vp->frame_sequence = 0;
+ vp->state |= ST_VP_PENDING;
+
+ if (!list_empty(&vp->pending_buf_q) &&
+ (!(vp->state & ST_VP_STREAMING) ||
+ !(vp->state & ST_VP_SENSOR_STREAMING))) {
+
+ camif_hw_enable_scaler(vp, vp->scaler.enable);
+ camif_hw_enable_capture(vp);
+ vp->state |= ST_VP_STREAMING;
+
+ if (!(vp->state & ST_VP_SENSOR_STREAMING)) {
+ vp->state |= ST_VP_SENSOR_STREAMING;
+ spin_unlock_irqrestore(&camif->slock, flags);
+ ret = sensor_set_streaming(camif, 1);
+ if (ret)
+ v4l2_err(&vp->vdev, "Sensor s_stream failed\n");
+ if (debug)
+ camif_hw_dump_regs(camif, __func__);
+
+ return ret;
+ }
+ }
+
+ spin_unlock_irqrestore(&camif->slock, flags);
+ return 0;
+}
+
+static void stop_streaming(struct vb2_queue *vq)
+{
+ struct camif_vp *vp = vb2_get_drv_priv(vq);
+ camif_stop_capture(vp);
+}
+
+static int queue_setup(struct vb2_queue *vq,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct camif_vp *vp = vb2_get_drv_priv(vq);
+ struct camif_frame *frame = &vp->out_frame;
+ const struct camif_fmt *fmt = vp->out_fmt;
+ unsigned int size;
+
+ if (fmt == NULL)
+ return -EINVAL;
+
+ size = (frame->f_width * frame->f_height * fmt->depth) / 8;
+
+ if (*num_planes)
+ return sizes[0] < size ? -EINVAL : 0;
+
+ *num_planes = 1;
+ sizes[0] = size;
+
+ pr_debug("size: %u\n", sizes[0]);
+ return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+ struct camif_vp *vp = vb2_get_drv_priv(vb->vb2_queue);
+
+ if (vp->out_fmt == NULL)
+ return -EINVAL;
+
+ if (vb2_plane_size(vb, 0) < vp->payload) {
+ v4l2_err(&vp->vdev, "buffer too small: %lu, required: %u\n",
+ vb2_plane_size(vb, 0), vp->payload);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb, 0, vp->payload);
+
+ return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct camif_buffer *buf = container_of(vbuf, struct camif_buffer, vb);
+ struct camif_vp *vp = vb2_get_drv_priv(vb->vb2_queue);
+ struct camif_dev *camif = vp->camif;
+ unsigned long flags;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ WARN_ON(camif_prepare_addr(vp, &buf->vb.vb2_buf, &buf->paddr));
+
+ if (!(vp->state & ST_VP_STREAMING) && vp->active_buffers < 2) {
+ /* Schedule an empty buffer in H/W */
+ buf->index = vp->buf_index;
+
+ camif_hw_set_output_addr(vp, &buf->paddr, buf->index);
+ camif_hw_set_output_addr(vp, &buf->paddr, buf->index + 2);
+
+ camif_active_queue_add(vp, buf);
+ vp->buf_index = !vp->buf_index;
+ } else {
+ camif_pending_queue_add(vp, buf);
+ }
+
+ if (vb2_is_streaming(&vp->vb_queue) && !list_empty(&vp->pending_buf_q)
+ && !(vp->state & ST_VP_STREAMING)) {
+
+ vp->state |= ST_VP_STREAMING;
+ camif_hw_enable_scaler(vp, vp->scaler.enable);
+ camif_hw_enable_capture(vp);
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ if (!(vp->state & ST_VP_SENSOR_STREAMING)) {
+ if (sensor_set_streaming(camif, 1) == 0)
+ vp->state |= ST_VP_SENSOR_STREAMING;
+ else
+ v4l2_err(&vp->vdev, "Sensor s_stream failed\n");
+
+ if (debug)
+ camif_hw_dump_regs(camif, __func__);
+ }
+ return;
+ }
+ spin_unlock_irqrestore(&camif->slock, flags);
+}
+
+static const struct vb2_ops s3c_camif_qops = {
+ .queue_setup = queue_setup,
+ .buf_prepare = buffer_prepare,
+ .buf_queue = buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = start_streaming,
+ .stop_streaming = stop_streaming,
+};
+
+static int s3c_camif_open(struct file *file)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ int ret;
+
+ pr_debug("[vp%d] state: %#x, owner: %p, pid: %d\n", vp->id,
+ vp->state, vp->owner, task_pid_nr(current));
+
+ if (mutex_lock_interruptible(&camif->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ ret = pm_runtime_resume_and_get(camif->dev);
+ if (ret < 0)
+ goto err_pm;
+
+ ret = sensor_set_power(camif, 1);
+ if (!ret)
+ goto unlock;
+
+ pm_runtime_put(camif->dev);
+err_pm:
+ v4l2_fh_release(file);
+unlock:
+ mutex_unlock(&camif->lock);
+ return ret;
+}
+
+static int s3c_camif_close(struct file *file)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ int ret;
+
+ pr_debug("[vp%d] state: %#x, owner: %p, pid: %d\n", vp->id,
+ vp->state, vp->owner, task_pid_nr(current));
+
+ mutex_lock(&camif->lock);
+
+ if (vp->owner == file->private_data) {
+ camif_stop_capture(vp);
+ vb2_queue_release(&vp->vb_queue);
+ vp->owner = NULL;
+ }
+
+ sensor_set_power(camif, 0);
+
+ pm_runtime_put(camif->dev);
+ ret = v4l2_fh_release(file);
+
+ mutex_unlock(&camif->lock);
+ return ret;
+}
+
+static __poll_t s3c_camif_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ __poll_t ret;
+
+ mutex_lock(&camif->lock);
+ if (vp->owner && vp->owner != file->private_data)
+ ret = EPOLLERR;
+ else
+ ret = vb2_poll(&vp->vb_queue, file, wait);
+
+ mutex_unlock(&camif->lock);
+ return ret;
+}
+
+static int s3c_camif_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ int ret;
+
+ if (vp->owner && vp->owner != file->private_data)
+ ret = -EBUSY;
+ else
+ ret = vb2_mmap(&vp->vb_queue, vma);
+
+ return ret;
+}
+
+static const struct v4l2_file_operations s3c_camif_fops = {
+ .owner = THIS_MODULE,
+ .open = s3c_camif_open,
+ .release = s3c_camif_close,
+ .poll = s3c_camif_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = s3c_camif_mmap,
+};
+
+/*
+ * Video node IOCTLs
+ */
+
+static int s3c_camif_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct camif_vp *vp = video_drvdata(file);
+
+ strscpy(cap->driver, S3C_CAMIF_DRIVER_NAME, sizeof(cap->driver));
+ strscpy(cap->card, S3C_CAMIF_DRIVER_NAME, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d",
+ dev_name(vp->camif->dev), vp->id);
+ return 0;
+}
+
+static int s3c_camif_vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct v4l2_subdev *sensor = vp->camif->sensor.sd;
+
+ if (input->index || sensor == NULL)
+ return -EINVAL;
+
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ strscpy(input->name, sensor->name, sizeof(input->name));
+ return 0;
+}
+
+static int s3c_camif_vidioc_s_input(struct file *file, void *priv,
+ unsigned int i)
+{
+ return i == 0 ? 0 : -EINVAL;
+}
+
+static int s3c_camif_vidioc_g_input(struct file *file, void *priv,
+ unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int s3c_camif_vidioc_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ const struct camif_fmt *fmt;
+
+ fmt = s3c_camif_find_format(vp, NULL, f->index);
+ if (!fmt)
+ return -EINVAL;
+
+ f->pixelformat = fmt->fourcc;
+ return 0;
+}
+
+static int s3c_camif_vidioc_g_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct camif_frame *frame = &vp->out_frame;
+ const struct camif_fmt *fmt = vp->out_fmt;
+
+ pix->bytesperline = frame->f_width * fmt->ybpp;
+ pix->sizeimage = vp->payload;
+
+ pix->pixelformat = fmt->fourcc;
+ pix->width = frame->f_width;
+ pix->height = frame->f_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+
+ return 0;
+}
+
+static int __camif_video_try_format(struct camif_vp *vp,
+ struct v4l2_pix_format *pix,
+ const struct camif_fmt **ffmt)
+{
+ struct camif_dev *camif = vp->camif;
+ struct v4l2_rect *crop = &camif->camif_crop;
+ unsigned int wmin, hmin, sc_hrmax, sc_vrmax;
+ const struct vp_pix_limits *pix_lim;
+ const struct camif_fmt *fmt;
+
+ fmt = s3c_camif_find_format(vp, &pix->pixelformat, 0);
+
+ if (WARN_ON(fmt == NULL))
+ return -EINVAL;
+
+ if (ffmt)
+ *ffmt = fmt;
+
+ pix_lim = &camif->variant->vp_pix_limits[vp->id];
+
+ pr_debug("fmt: %ux%u, crop: %ux%u, bytesperline: %u\n",
+ pix->width, pix->height, crop->width, crop->height,
+ pix->bytesperline);
+ /*
+ * Calculate minimum width and height according to the configured
+ * camera input interface crop rectangle and the resizer's capabilities.
+ */
+ sc_hrmax = min(SCALER_MAX_RATIO, 1 << (ffs(crop->width) - 3));
+ sc_vrmax = min(SCALER_MAX_RATIO, 1 << (ffs(crop->height) - 1));
+
+ wmin = max_t(u32, pix_lim->min_out_width, crop->width / sc_hrmax);
+ wmin = round_up(wmin, pix_lim->out_width_align);
+ hmin = max_t(u32, 8, crop->height / sc_vrmax);
+ hmin = round_up(hmin, 8);
+
+ v4l_bound_align_image(&pix->width, wmin, pix_lim->max_sc_out_width,
+ ffs(pix_lim->out_width_align) - 1,
+ &pix->height, hmin, pix_lim->max_height, 0, 0);
+
+ pix->bytesperline = pix->width * fmt->ybpp;
+ pix->sizeimage = (pix->width * pix->height * fmt->depth) / 8;
+ pix->pixelformat = fmt->fourcc;
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ pix->field = V4L2_FIELD_NONE;
+
+ pr_debug("%ux%u, wmin: %d, hmin: %d, sc_hrmax: %d, sc_vrmax: %d\n",
+ pix->width, pix->height, wmin, hmin, sc_hrmax, sc_vrmax);
+
+ return 0;
+}
+
+static int s3c_camif_vidioc_try_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ return __camif_video_try_format(vp, &f->fmt.pix, NULL);
+}
+
+static int s3c_camif_vidioc_s_fmt(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_frame *out_frame = &vp->out_frame;
+ const struct camif_fmt *fmt = NULL;
+ int ret;
+
+ pr_debug("[vp%d]\n", vp->id);
+
+ if (vb2_is_busy(&vp->vb_queue))
+ return -EBUSY;
+
+ ret = __camif_video_try_format(vp, &f->fmt.pix, &fmt);
+ if (ret < 0)
+ return ret;
+
+ vp->out_fmt = fmt;
+ vp->payload = pix->sizeimage;
+ out_frame->f_width = pix->width;
+ out_frame->f_height = pix->height;
+
+ /* Reset composition rectangle */
+ out_frame->rect.width = pix->width;
+ out_frame->rect.height = pix->height;
+ out_frame->rect.left = 0;
+ out_frame->rect.top = 0;
+
+ if (vp->owner == NULL)
+ vp->owner = priv;
+
+ pr_debug("%ux%u. payload: %u. fmt: 0x%08x. %d %d. sizeimage: %d. bpl: %d\n",
+ out_frame->f_width, out_frame->f_height, vp->payload,
+ fmt->fourcc, pix->width * pix->height * fmt->depth,
+ fmt->depth, pix->sizeimage, pix->bytesperline);
+
+ return 0;
+}
+
+/* Only check pixel formats at the sensor and the camif subdev pads */
+static int camif_pipeline_validate(struct camif_dev *camif)
+{
+ struct v4l2_subdev_format src_fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct media_pad *pad;
+ int ret;
+
+ /* Retrieve format at the sensor subdev source pad */
+ pad = media_pad_remote_pad_first(&camif->pads[0]);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ return -EPIPE;
+
+ src_fmt.pad = pad->index;
+ ret = v4l2_subdev_call(camif->sensor.sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EPIPE;
+
+ if (src_fmt.format.width != camif->mbus_fmt.width ||
+ src_fmt.format.height != camif->mbus_fmt.height ||
+ src_fmt.format.code != camif->mbus_fmt.code)
+ return -EPIPE;
+
+ return 0;
+}
+
+static int s3c_camif_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ struct media_entity *sensor = &camif->sensor.sd->entity;
+ int ret;
+
+ pr_debug("[vp%d]\n", vp->id);
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ if (s3c_vp_active(vp))
+ return 0;
+
+ ret = media_pipeline_start(sensor->pads, camif->m_pipeline);
+ if (ret < 0)
+ return ret;
+
+ ret = camif_pipeline_validate(camif);
+ if (ret < 0) {
+ media_pipeline_stop(sensor->pads);
+ return ret;
+ }
+
+ return vb2_streamon(&vp->vb_queue, type);
+}
+
+static int s3c_camif_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ int ret;
+
+ pr_debug("[vp%d]\n", vp->id);
+
+ if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ ret = vb2_streamoff(&vp->vb_queue, type);
+ if (ret == 0)
+ media_pipeline_stop(camif->sensor.sd->entity.pads);
+ return ret;
+}
+
+static int s3c_camif_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *rb)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ int ret;
+
+ pr_debug("[vp%d] rb count: %d, owner: %p, priv: %p\n",
+ vp->id, rb->count, vp->owner, priv);
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ if (rb->count)
+ rb->count = max_t(u32, CAMIF_REQ_BUFS_MIN, rb->count);
+ else
+ vp->owner = NULL;
+
+ ret = vb2_reqbufs(&vp->vb_queue, rb);
+ if (ret < 0)
+ return ret;
+
+ if (rb->count && rb->count < CAMIF_REQ_BUFS_MIN) {
+ rb->count = 0;
+ vb2_reqbufs(&vp->vb_queue, rb);
+ ret = -ENOMEM;
+ }
+
+ vp->reqbufs_count = rb->count;
+ if (vp->owner == NULL && rb->count > 0)
+ vp->owner = priv;
+
+ return ret;
+}
+
+static int s3c_camif_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ return vb2_querybuf(&vp->vb_queue, buf);
+}
+
+static int s3c_camif_qbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct camif_vp *vp = video_drvdata(file);
+
+ pr_debug("[vp%d]\n", vp->id);
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ return vb2_qbuf(&vp->vb_queue, vp->vdev.v4l2_dev->mdev, buf);
+}
+
+static int s3c_camif_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct camif_vp *vp = video_drvdata(file);
+
+ pr_debug("[vp%d] sequence: %d\n", vp->id, vp->frame_sequence);
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ return vb2_dqbuf(&vp->vb_queue, buf, file->f_flags & O_NONBLOCK);
+}
+
+static int s3c_camif_create_bufs(struct file *file, void *priv,
+ struct v4l2_create_buffers *create)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ int ret;
+
+ if (vp->owner && vp->owner != priv)
+ return -EBUSY;
+
+ create->count = max_t(u32, 1, create->count);
+ ret = vb2_create_bufs(&vp->vb_queue, create);
+
+ if (!ret && vp->owner == NULL)
+ vp->owner = priv;
+
+ return ret;
+}
+
+static int s3c_camif_prepare_buf(struct file *file, void *priv,
+ struct v4l2_buffer *b)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ return vb2_prepare_buf(&vp->vb_queue, vp->vdev.v4l2_dev->mdev, b);
+}
+
+static int s3c_camif_g_selection(struct file *file, void *priv,
+ struct v4l2_selection *sel)
+{
+ struct camif_vp *vp = video_drvdata(file);
+
+ if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = vp->out_frame.f_width;
+ sel->r.height = vp->out_frame.f_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ sel->r = vp->out_frame.rect;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static void __camif_try_compose(struct camif_dev *camif, struct camif_vp *vp,
+ struct v4l2_rect *r)
+{
+ /* s3c244x doesn't support composition */
+ if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV) {
+ *r = vp->out_frame.rect;
+ return;
+ }
+
+ /* TODO: s3c64xx */
+}
+
+static int s3c_camif_s_selection(struct file *file, void *priv,
+ struct v4l2_selection *sel)
+{
+ struct camif_vp *vp = video_drvdata(file);
+ struct camif_dev *camif = vp->camif;
+ struct v4l2_rect rect = sel->r;
+ unsigned long flags;
+
+ if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+ sel->target != V4L2_SEL_TGT_COMPOSE)
+ return -EINVAL;
+
+ __camif_try_compose(camif, vp, &rect);
+
+ sel->r = rect;
+ spin_lock_irqsave(&camif->slock, flags);
+ vp->out_frame.rect = rect;
+ vp->state |= ST_VP_CONFIG;
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ pr_debug("type: %#x, target: %#x, flags: %#x, (%d,%d)/%dx%d\n",
+ sel->type, sel->target, sel->flags,
+ sel->r.left, sel->r.top, sel->r.width, sel->r.height);
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops s3c_camif_ioctl_ops = {
+ .vidioc_querycap = s3c_camif_vidioc_querycap,
+ .vidioc_enum_input = s3c_camif_vidioc_enum_input,
+ .vidioc_g_input = s3c_camif_vidioc_g_input,
+ .vidioc_s_input = s3c_camif_vidioc_s_input,
+ .vidioc_enum_fmt_vid_cap = s3c_camif_vidioc_enum_fmt,
+ .vidioc_try_fmt_vid_cap = s3c_camif_vidioc_try_fmt,
+ .vidioc_s_fmt_vid_cap = s3c_camif_vidioc_s_fmt,
+ .vidioc_g_fmt_vid_cap = s3c_camif_vidioc_g_fmt,
+ .vidioc_g_selection = s3c_camif_g_selection,
+ .vidioc_s_selection = s3c_camif_s_selection,
+ .vidioc_reqbufs = s3c_camif_reqbufs,
+ .vidioc_querybuf = s3c_camif_querybuf,
+ .vidioc_prepare_buf = s3c_camif_prepare_buf,
+ .vidioc_create_bufs = s3c_camif_create_bufs,
+ .vidioc_qbuf = s3c_camif_qbuf,
+ .vidioc_dqbuf = s3c_camif_dqbuf,
+ .vidioc_streamon = s3c_camif_streamon,
+ .vidioc_streamoff = s3c_camif_streamoff,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+};
+
+/*
+ * Video node controls
+ */
+static int s3c_camif_video_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct camif_vp *vp = ctrl->priv;
+ struct camif_dev *camif = vp->camif;
+ unsigned long flags;
+
+ pr_debug("[vp%d] ctrl: %s, value: %d\n", vp->id,
+ ctrl->name, ctrl->val);
+
+ spin_lock_irqsave(&camif->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ vp->hflip = ctrl->val;
+ break;
+
+ case V4L2_CID_VFLIP:
+ vp->vflip = ctrl->val;
+ break;
+ }
+
+ vp->state |= ST_VP_CONFIG;
+ spin_unlock_irqrestore(&camif->slock, flags);
+ return 0;
+}
+
+/* Codec and preview video node control ops */
+static const struct v4l2_ctrl_ops s3c_camif_video_ctrl_ops = {
+ .s_ctrl = s3c_camif_video_s_ctrl,
+};
+
+int s3c_camif_register_video_node(struct camif_dev *camif, int idx)
+{
+ struct camif_vp *vp = &camif->vp[idx];
+ struct vb2_queue *q = &vp->vb_queue;
+ struct video_device *vfd = &vp->vdev;
+ struct v4l2_ctrl *ctrl;
+ int ret;
+
+ memset(vfd, 0, sizeof(*vfd));
+ snprintf(vfd->name, sizeof(vfd->name), "camif-%s",
+ vp->id == 0 ? "codec" : "preview");
+
+ vfd->fops = &s3c_camif_fops;
+ vfd->ioctl_ops = &s3c_camif_ioctl_ops;
+ vfd->v4l2_dev = &camif->v4l2_dev;
+ vfd->minor = -1;
+ vfd->release = video_device_release_empty;
+ vfd->lock = &camif->lock;
+ vp->reqbufs_count = 0;
+
+ INIT_LIST_HEAD(&vp->pending_buf_q);
+ INIT_LIST_HEAD(&vp->active_buf_q);
+
+ memset(q, 0, sizeof(*q));
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->ops = &s3c_camif_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct camif_buffer);
+ q->drv_priv = vp;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &vp->camif->lock;
+ q->dev = camif->v4l2_dev.dev;
+
+ ret = vb2_queue_init(q);
+ if (ret)
+ return ret;
+
+ vp->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vfd->entity, 1, &vp->pad);
+ if (ret)
+ return ret;
+
+ video_set_drvdata(vfd, vp);
+
+ v4l2_ctrl_handler_init(&vp->ctrl_handler, 1);
+ ctrl = v4l2_ctrl_new_std(&vp->ctrl_handler, &s3c_camif_video_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ if (ctrl)
+ ctrl->priv = vp;
+ ctrl = v4l2_ctrl_new_std(&vp->ctrl_handler, &s3c_camif_video_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ if (ctrl)
+ ctrl->priv = vp;
+
+ ret = vp->ctrl_handler.error;
+ if (ret < 0)
+ goto err_me_cleanup;
+
+ vfd->ctrl_handler = &vp->ctrl_handler;
+ vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
+
+ ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
+ if (ret)
+ goto err_ctrlh_free;
+
+ v4l2_info(&camif->v4l2_dev, "registered %s as /dev/%s\n",
+ vfd->name, video_device_node_name(vfd));
+ return 0;
+
+err_ctrlh_free:
+ v4l2_ctrl_handler_free(&vp->ctrl_handler);
+err_me_cleanup:
+ media_entity_cleanup(&vfd->entity);
+ return ret;
+}
+
+void s3c_camif_unregister_video_node(struct camif_dev *camif, int idx)
+{
+ struct video_device *vfd = &camif->vp[idx].vdev;
+
+ if (video_is_registered(vfd)) {
+ video_unregister_device(vfd);
+ media_entity_cleanup(&vfd->entity);
+ v4l2_ctrl_handler_free(vfd->ctrl_handler);
+ }
+}
+
+/* Media bus pixel formats supported at the camif input */
+static const u32 camif_mbus_formats[] = {
+ MEDIA_BUS_FMT_YUYV8_2X8,
+ MEDIA_BUS_FMT_YVYU8_2X8,
+ MEDIA_BUS_FMT_UYVY8_2X8,
+ MEDIA_BUS_FMT_VYUY8_2X8,
+};
+
+/*
+ * Camera input interface subdev operations
+ */
+
+static int s3c_camif_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->index >= ARRAY_SIZE(camif_mbus_formats))
+ return -EINVAL;
+
+ code->code = camif_mbus_formats[code->index];
+ return 0;
+}
+
+static int s3c_camif_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct camif_dev *camif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ fmt->format = *mf;
+ return 0;
+ }
+
+ mutex_lock(&camif->lock);
+
+ switch (fmt->pad) {
+ case CAMIF_SD_PAD_SINK:
+ /* full camera input pixel size */
+ *mf = camif->mbus_fmt;
+ break;
+
+ case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P:
+ /* crop rectangle at camera interface input */
+ mf->width = camif->camif_crop.width;
+ mf->height = camif->camif_crop.height;
+ mf->code = camif->mbus_fmt.code;
+ break;
+ }
+
+ mutex_unlock(&camif->lock);
+ mf->field = V4L2_FIELD_NONE;
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+ return 0;
+}
+
+static void __camif_subdev_try_format(struct camif_dev *camif,
+ struct v4l2_mbus_framefmt *mf, int pad)
+{
+ const struct s3c_camif_variant *variant = camif->variant;
+ const struct vp_pix_limits *pix_lim;
+ unsigned int i;
+
+ /* FIXME: constraints against codec or preview path ? */
+ pix_lim = &variant->vp_pix_limits[VP_CODEC];
+
+ for (i = 0; i < ARRAY_SIZE(camif_mbus_formats); i++)
+ if (camif_mbus_formats[i] == mf->code)
+ break;
+
+ if (i == ARRAY_SIZE(camif_mbus_formats))
+ mf->code = camif_mbus_formats[0];
+
+ if (pad == CAMIF_SD_PAD_SINK) {
+ v4l_bound_align_image(&mf->width, 8, CAMIF_MAX_PIX_WIDTH,
+ ffs(pix_lim->out_width_align) - 1,
+ &mf->height, 8, CAMIF_MAX_PIX_HEIGHT, 0,
+ 0);
+ } else {
+ struct v4l2_rect *crop = &camif->camif_crop;
+ v4l_bound_align_image(&mf->width, 8, crop->width,
+ ffs(pix_lim->out_width_align) - 1,
+ &mf->height, 8, crop->height,
+ 0, 0);
+ }
+
+ v4l2_dbg(1, debug, &camif->subdev, "%ux%u\n", mf->width, mf->height);
+}
+
+static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct camif_dev *camif = v4l2_get_subdevdata(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct v4l2_rect *crop = &camif->camif_crop;
+ int i;
+
+ v4l2_dbg(1, debug, sd, "pad%d: code: 0x%x, %ux%u\n",
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ mf->field = V4L2_FIELD_NONE;
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+ mutex_lock(&camif->lock);
+
+ /*
+ * No pixel format change at the camera input is allowed
+ * while streaming.
+ */
+ if (vb2_is_busy(&camif->vp[VP_CODEC].vb_queue) ||
+ vb2_is_busy(&camif->vp[VP_PREVIEW].vb_queue)) {
+ mutex_unlock(&camif->lock);
+ return -EBUSY;
+ }
+
+ __camif_subdev_try_format(camif, mf, fmt->pad);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
+ *mf = fmt->format;
+ mutex_unlock(&camif->lock);
+ return 0;
+ }
+
+ switch (fmt->pad) {
+ case CAMIF_SD_PAD_SINK:
+ camif->mbus_fmt = *mf;
+ /* Reset sink crop rectangle. */
+ crop->width = mf->width;
+ crop->height = mf->height;
+ crop->left = 0;
+ crop->top = 0;
+ /*
+ * Reset source format (the camif's crop rectangle)
+ * and the video output resolution.
+ */
+ for (i = 0; i < CAMIF_VP_NUM; i++) {
+ struct camif_frame *frame = &camif->vp[i].out_frame;
+ frame->rect = *crop;
+ frame->f_width = mf->width;
+ frame->f_height = mf->height;
+ }
+ break;
+
+ case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P:
+ /* Pixel format can be only changed on the sink pad. */
+ mf->code = camif->mbus_fmt.code;
+ mf->width = crop->width;
+ mf->height = crop->height;
+ break;
+ }
+
+ mutex_unlock(&camif->lock);
+ return 0;
+}
+
+static int s3c_camif_subdev_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct camif_dev *camif = v4l2_get_subdevdata(sd);
+ struct v4l2_rect *crop = &camif->camif_crop;
+ struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt;
+
+ if ((sel->target != V4L2_SEL_TGT_CROP &&
+ sel->target != V4L2_SEL_TGT_CROP_BOUNDS) ||
+ sel->pad != CAMIF_SD_PAD_SINK)
+ return -EINVAL;
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad);
+ return 0;
+ }
+
+ mutex_lock(&camif->lock);
+
+ if (sel->target == V4L2_SEL_TGT_CROP) {
+ sel->r = *crop;
+ } else { /* crop bounds */
+ sel->r.width = mf->width;
+ sel->r.height = mf->height;
+ sel->r.left = 0;
+ sel->r.top = 0;
+ }
+
+ mutex_unlock(&camif->lock);
+
+ v4l2_dbg(1, debug, sd, "%s: crop: (%d,%d) %dx%d, size: %ux%u\n",
+ __func__, crop->left, crop->top, crop->width,
+ crop->height, mf->width, mf->height);
+
+ return 0;
+}
+
+static void __camif_try_crop(struct camif_dev *camif, struct v4l2_rect *r)
+{
+ struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt;
+ const struct camif_pix_limits *pix_lim = &camif->variant->pix_limits;
+ unsigned int left = 2 * r->left;
+ unsigned int top = 2 * r->top;
+
+ /*
+ * Following constraints must be met:
+ * - r->width + 2 * r->left = mf->width;
+ * - r->height + 2 * r->top = mf->height;
+ * - crop rectangle size and position must be aligned
+ * to 8 or 2 pixels, depending on SoC version.
+ */
+ v4l_bound_align_image(&r->width, 0, mf->width,
+ ffs(pix_lim->win_hor_offset_align) - 1,
+ &r->height, 0, mf->height, 1, 0);
+
+ v4l_bound_align_image(&left, 0, mf->width - r->width,
+ ffs(pix_lim->win_hor_offset_align),
+ &top, 0, mf->height - r->height, 2, 0);
+
+ r->left = left / 2;
+ r->top = top / 2;
+ r->width = mf->width - left;
+ r->height = mf->height - top;
+ /*
+ * Make sure we either downscale or upscale both the pixel
+ * width and height. Just return current crop rectangle if
+ * this scaler constraint is not met.
+ */
+ if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV &&
+ camif_is_streaming(camif)) {
+ unsigned int i;
+
+ for (i = 0; i < CAMIF_VP_NUM; i++) {
+ struct v4l2_rect *or = &camif->vp[i].out_frame.rect;
+ if ((or->width > r->width) == (or->height > r->height))
+ continue;
+ *r = camif->camif_crop;
+ pr_debug("Width/height scaling direction limitation\n");
+ break;
+ }
+ }
+
+ v4l2_dbg(1, debug, &camif->v4l2_dev, "crop: (%d,%d)/%dx%d, fmt: %ux%u\n",
+ r->left, r->top, r->width, r->height, mf->width, mf->height);
+}
+
+static int s3c_camif_subdev_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct camif_dev *camif = v4l2_get_subdevdata(sd);
+ struct v4l2_rect *crop = &camif->camif_crop;
+ struct camif_scaler scaler;
+
+ if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != CAMIF_SD_PAD_SINK)
+ return -EINVAL;
+
+ mutex_lock(&camif->lock);
+ __camif_try_crop(camif, &sel->r);
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad) = sel->r;
+ } else {
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&camif->slock, flags);
+ *crop = sel->r;
+
+ for (i = 0; i < CAMIF_VP_NUM; i++) {
+ struct camif_vp *vp = &camif->vp[i];
+ scaler = vp->scaler;
+ if (s3c_camif_get_scaler_config(vp, &scaler))
+ continue;
+ vp->scaler = scaler;
+ vp->state |= ST_VP_CONFIG;
+ }
+
+ spin_unlock_irqrestore(&camif->slock, flags);
+ }
+ mutex_unlock(&camif->lock);
+
+ v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %u, f_h: %u\n",
+ __func__, crop->left, crop->top, crop->width, crop->height,
+ camif->mbus_fmt.width, camif->mbus_fmt.height);
+
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops s3c_camif_subdev_pad_ops = {
+ .enum_mbus_code = s3c_camif_subdev_enum_mbus_code,
+ .get_selection = s3c_camif_subdev_get_selection,
+ .set_selection = s3c_camif_subdev_set_selection,
+ .get_fmt = s3c_camif_subdev_get_fmt,
+ .set_fmt = s3c_camif_subdev_set_fmt,
+};
+
+static const struct v4l2_subdev_ops s3c_camif_subdev_ops = {
+ .pad = &s3c_camif_subdev_pad_ops,
+};
+
+static int s3c_camif_subdev_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct camif_dev *camif = container_of(ctrl->handler, struct camif_dev,
+ ctrl_handler);
+ unsigned long flags;
+
+ spin_lock_irqsave(&camif->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_COLORFX:
+ camif->colorfx = camif->ctrl_colorfx->val;
+ /* Set Cb, Cr */
+ switch (ctrl->val) {
+ case V4L2_COLORFX_SEPIA:
+ camif->colorfx_cb = 115;
+ camif->colorfx_cr = 145;
+ break;
+ case V4L2_COLORFX_SET_CBCR:
+ camif->colorfx_cb = camif->ctrl_colorfx_cbcr->val >> 8;
+ camif->colorfx_cr = camif->ctrl_colorfx_cbcr->val & 0xff;
+ break;
+ default:
+ /* for V4L2_COLORFX_BW and others */
+ camif->colorfx_cb = 128;
+ camif->colorfx_cr = 128;
+ }
+ break;
+ case V4L2_CID_TEST_PATTERN:
+ camif->test_pattern = camif->ctrl_test_pattern->val;
+ break;
+ default:
+ WARN_ON(1);
+ }
+
+ camif->vp[VP_CODEC].state |= ST_VP_CONFIG;
+ camif->vp[VP_PREVIEW].state |= ST_VP_CONFIG;
+ spin_unlock_irqrestore(&camif->slock, flags);
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops s3c_camif_subdev_ctrl_ops = {
+ .s_ctrl = s3c_camif_subdev_s_ctrl,
+};
+
+static const char * const s3c_camif_test_pattern_menu[] = {
+ "Disabled",
+ "Color bars",
+ "Horizontal increment",
+ "Vertical increment",
+};
+
+int s3c_camif_create_subdev(struct camif_dev *camif)
+{
+ struct v4l2_ctrl_handler *handler = &camif->ctrl_handler;
+ struct v4l2_subdev *sd = &camif->subdev;
+ int ret;
+
+ v4l2_subdev_init(sd, &s3c_camif_subdev_ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ strscpy(sd->name, "S3C-CAMIF", sizeof(sd->name));
+
+ camif->pads[CAMIF_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ camif->pads[CAMIF_SD_PAD_SOURCE_C].flags = MEDIA_PAD_FL_SOURCE;
+ camif->pads[CAMIF_SD_PAD_SOURCE_P].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity, CAMIF_SD_PADS_NUM,
+ camif->pads);
+ if (ret)
+ return ret;
+
+ v4l2_ctrl_handler_init(handler, 3);
+ camif->ctrl_test_pattern = v4l2_ctrl_new_std_menu_items(handler,
+ &s3c_camif_subdev_ctrl_ops, V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(s3c_camif_test_pattern_menu) - 1, 0, 0,
+ s3c_camif_test_pattern_menu);
+
+ if (camif->variant->has_img_effect) {
+ camif->ctrl_colorfx = v4l2_ctrl_new_std_menu(handler,
+ &s3c_camif_subdev_ctrl_ops,
+ V4L2_CID_COLORFX, V4L2_COLORFX_SET_CBCR,
+ ~0x981f, V4L2_COLORFX_NONE);
+
+ camif->ctrl_colorfx_cbcr = v4l2_ctrl_new_std(handler,
+ &s3c_camif_subdev_ctrl_ops,
+ V4L2_CID_COLORFX_CBCR, 0, 0xffff, 1, 0);
+ }
+
+ if (handler->error) {
+ v4l2_ctrl_handler_free(handler);
+ media_entity_cleanup(&sd->entity);
+ return handler->error;
+ }
+
+ if (camif->variant->has_img_effect)
+ v4l2_ctrl_auto_cluster(2, &camif->ctrl_colorfx,
+ V4L2_COLORFX_SET_CBCR, false);
+
+ sd->ctrl_handler = handler;
+ v4l2_set_subdevdata(sd, camif);
+
+ return 0;
+}
+
+void s3c_camif_unregister_subdev(struct camif_dev *camif)
+{
+ struct v4l2_subdev *sd = &camif->subdev;
+
+ /* Return if not registered */
+ if (v4l2_get_subdevdata(sd) == NULL)
+ return;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(&camif->ctrl_handler);
+ v4l2_set_subdevdata(sd, NULL);
+}
+
+int s3c_camif_set_defaults(struct camif_dev *camif)
+{
+ unsigned int ip_rev = camif->variant->ip_revision;
+ int i;
+
+ for (i = 0; i < CAMIF_VP_NUM; i++) {
+ struct camif_vp *vp = &camif->vp[i];
+ struct camif_frame *f = &vp->out_frame;
+
+ vp->camif = camif;
+ vp->id = i;
+ vp->offset = camif->variant->vp_offset;
+
+ if (ip_rev == S3C244X_CAMIF_IP_REV)
+ vp->fmt_flags = i ? FMT_FL_S3C24XX_PREVIEW :
+ FMT_FL_S3C24XX_CODEC;
+ else
+ vp->fmt_flags = FMT_FL_S3C64XX;
+
+ vp->out_fmt = s3c_camif_find_format(vp, NULL, 0);
+ BUG_ON(vp->out_fmt == NULL);
+
+ memset(f, 0, sizeof(*f));
+ f->f_width = CAMIF_DEF_WIDTH;
+ f->f_height = CAMIF_DEF_HEIGHT;
+ f->rect.width = CAMIF_DEF_WIDTH;
+ f->rect.height = CAMIF_DEF_HEIGHT;
+
+ /* Scaler is always enabled */
+ vp->scaler.enable = 1;
+
+ vp->payload = (f->f_width * f->f_height *
+ vp->out_fmt->depth) / 8;
+ }
+
+ memset(&camif->mbus_fmt, 0, sizeof(camif->mbus_fmt));
+ camif->mbus_fmt.width = CAMIF_DEF_WIDTH;
+ camif->mbus_fmt.height = CAMIF_DEF_HEIGHT;
+ camif->mbus_fmt.code = camif_mbus_formats[0];
+
+ memset(&camif->camif_crop, 0, sizeof(camif->camif_crop));
+ camif->camif_crop.width = CAMIF_DEF_WIDTH;
+ camif->camif_crop.height = CAMIF_DEF_HEIGHT;
+
+ return 0;
+}
diff --git a/drivers/media/platform/samsung/s3c-camif/camif-core.c b/drivers/media/platform/samsung/s3c-camif/camif-core.c
new file mode 100644
index 0000000000..e4529f666e
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/camif-core.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver
+ *
+ * Copyright (C) 2012 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
+ * Copyright (C) 2012 Tomasz Figa <tomasz.figa@gmail.com>
+ */
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "camif-core.h"
+
+static char *camif_clocks[CLK_MAX_NUM] = {
+ /* HCLK CAMIF clock */
+ [CLK_GATE] = "camif",
+ /* CAMIF / external camera sensor master clock */
+ [CLK_CAM] = "camera",
+};
+
+static const struct camif_fmt camif_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_YUV422P,
+ .depth = 16,
+ .ybpp = 1,
+ .color = IMG_FMT_YCBCR422P,
+ .colplanes = 3,
+ .flags = FMT_FL_S3C24XX_CODEC |
+ FMT_FL_S3C64XX,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .depth = 12,
+ .ybpp = 1,
+ .color = IMG_FMT_YCBCR420,
+ .colplanes = 3,
+ .flags = FMT_FL_S3C24XX_CODEC |
+ FMT_FL_S3C64XX,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVU420,
+ .depth = 12,
+ .ybpp = 1,
+ .color = IMG_FMT_YCRCB420,
+ .colplanes = 3,
+ .flags = FMT_FL_S3C24XX_CODEC |
+ FMT_FL_S3C64XX,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB565X,
+ .depth = 16,
+ .ybpp = 2,
+ .color = IMG_FMT_RGB565,
+ .colplanes = 1,
+ .flags = FMT_FL_S3C24XX_PREVIEW |
+ FMT_FL_S3C64XX,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = 32,
+ .ybpp = 4,
+ .color = IMG_FMT_XRGB8888,
+ .colplanes = 1,
+ .flags = FMT_FL_S3C24XX_PREVIEW |
+ FMT_FL_S3C64XX,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR666,
+ .depth = 32,
+ .ybpp = 4,
+ .color = IMG_FMT_RGB666,
+ .colplanes = 1,
+ .flags = FMT_FL_S3C64XX,
+ }
+};
+
+/**
+ * s3c_camif_find_format() - lookup camif color format by fourcc or an index
+ * @vp: video path (DMA) description (codec/preview)
+ * @pixelformat: fourcc to match, ignored if null
+ * @index: index to the camif_formats array, ignored if negative
+ */
+const struct camif_fmt *s3c_camif_find_format(struct camif_vp *vp,
+ const u32 *pixelformat,
+ int index)
+{
+ const struct camif_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)ARRAY_SIZE(camif_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(camif_formats); ++i) {
+ fmt = &camif_formats[i];
+ if (vp && !(vp->fmt_flags & fmt->flags))
+ continue;
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+static int camif_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift)
+{
+ unsigned int sh = 6;
+
+ if (src >= 64 * tar)
+ return -EINVAL;
+
+ while (sh--) {
+ unsigned int tmp = 1 << sh;
+ if (src >= tar * tmp) {
+ *shift = sh;
+ *ratio = tmp;
+ return 0;
+ }
+ }
+ *shift = 0;
+ *ratio = 1;
+ return 0;
+}
+
+int s3c_camif_get_scaler_config(struct camif_vp *vp,
+ struct camif_scaler *scaler)
+{
+ struct v4l2_rect *camif_crop = &vp->camif->camif_crop;
+ int source_x = camif_crop->width;
+ int source_y = camif_crop->height;
+ int target_x = vp->out_frame.rect.width;
+ int target_y = vp->out_frame.rect.height;
+ int ret;
+
+ if (vp->rotation == 90 || vp->rotation == 270)
+ swap(target_x, target_y);
+
+ ret = camif_get_scaler_factor(source_x, target_x, &scaler->pre_h_ratio,
+ &scaler->h_shift);
+ if (ret < 0)
+ return ret;
+
+ ret = camif_get_scaler_factor(source_y, target_y, &scaler->pre_v_ratio,
+ &scaler->v_shift);
+ if (ret < 0)
+ return ret;
+
+ scaler->pre_dst_width = source_x / scaler->pre_h_ratio;
+ scaler->pre_dst_height = source_y / scaler->pre_v_ratio;
+
+ scaler->main_h_ratio = (source_x << 8) / (target_x << scaler->h_shift);
+ scaler->main_v_ratio = (source_y << 8) / (target_y << scaler->v_shift);
+
+ scaler->scaleup_h = (target_x >= source_x);
+ scaler->scaleup_v = (target_y >= source_y);
+
+ scaler->copy = 0;
+
+ pr_debug("H: ratio: %u, shift: %u. V: ratio: %u, shift: %u.\n",
+ scaler->pre_h_ratio, scaler->h_shift,
+ scaler->pre_v_ratio, scaler->v_shift);
+
+ pr_debug("Source: %dx%d, Target: %dx%d, scaleup_h/v: %d/%d\n",
+ source_x, source_y, target_x, target_y,
+ scaler->scaleup_h, scaler->scaleup_v);
+
+ return 0;
+}
+
+static int camif_register_sensor(struct camif_dev *camif)
+{
+ struct s3c_camif_sensor_info *sensor = &camif->pdata.sensor;
+ struct v4l2_device *v4l2_dev = &camif->v4l2_dev;
+ struct i2c_adapter *adapter;
+ struct v4l2_subdev_format format = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct v4l2_subdev *sd;
+ int ret;
+
+ camif->sensor.sd = NULL;
+
+ if (sensor->i2c_board_info.addr == 0)
+ return -EINVAL;
+
+ adapter = i2c_get_adapter(sensor->i2c_bus_num);
+ if (adapter == NULL) {
+ v4l2_warn(v4l2_dev, "failed to get I2C adapter %d\n",
+ sensor->i2c_bus_num);
+ return -EPROBE_DEFER;
+ }
+
+ sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter,
+ &sensor->i2c_board_info, NULL);
+ if (sd == NULL) {
+ i2c_put_adapter(adapter);
+ v4l2_warn(v4l2_dev, "failed to acquire subdev %s\n",
+ sensor->i2c_board_info.type);
+ return -EPROBE_DEFER;
+ }
+ camif->sensor.sd = sd;
+
+ v4l2_info(v4l2_dev, "registered sensor subdevice %s\n", sd->name);
+
+ /* Get initial pixel format and set it at the camif sink pad */
+ format.pad = 0;
+ ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &format);
+
+ if (ret < 0)
+ return 0;
+
+ format.pad = CAMIF_SD_PAD_SINK;
+ v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, &format);
+
+ v4l2_info(sd, "Initial format from sensor: %dx%d, %#x\n",
+ format.format.width, format.format.height,
+ format.format.code);
+ return 0;
+}
+
+static void camif_unregister_sensor(struct camif_dev *camif)
+{
+ struct v4l2_subdev *sd = camif->sensor.sd;
+ struct i2c_client *client = sd ? v4l2_get_subdevdata(sd) : NULL;
+ struct i2c_adapter *adapter;
+
+ if (client == NULL)
+ return;
+
+ adapter = client->adapter;
+ v4l2_device_unregister_subdev(sd);
+ camif->sensor.sd = NULL;
+ i2c_unregister_device(client);
+ i2c_put_adapter(adapter);
+}
+
+static int camif_create_media_links(struct camif_dev *camif)
+{
+ int i, ret;
+
+ ret = media_create_pad_link(&camif->sensor.sd->entity, 0,
+ &camif->subdev.entity, CAMIF_SD_PAD_SINK,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ return ret;
+
+ for (i = 1; i < CAMIF_SD_PADS_NUM && !ret; i++) {
+ ret = media_create_pad_link(&camif->subdev.entity, i,
+ &camif->vp[i - 1].vdev.entity, 0,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ }
+
+ return ret;
+}
+
+static int camif_register_video_nodes(struct camif_dev *camif)
+{
+ int ret = s3c_camif_register_video_node(camif, VP_CODEC);
+ if (ret < 0)
+ return ret;
+
+ return s3c_camif_register_video_node(camif, VP_PREVIEW);
+}
+
+static void camif_unregister_video_nodes(struct camif_dev *camif)
+{
+ s3c_camif_unregister_video_node(camif, VP_CODEC);
+ s3c_camif_unregister_video_node(camif, VP_PREVIEW);
+}
+
+static void camif_unregister_media_entities(struct camif_dev *camif)
+{
+ camif_unregister_video_nodes(camif);
+ camif_unregister_sensor(camif);
+}
+
+/*
+ * Media device
+ */
+static int camif_media_dev_init(struct camif_dev *camif)
+{
+ struct media_device *md = &camif->media_dev;
+ struct v4l2_device *v4l2_dev = &camif->v4l2_dev;
+ unsigned int ip_rev = camif->variant->ip_revision;
+ int ret;
+
+ memset(md, 0, sizeof(*md));
+ snprintf(md->model, sizeof(md->model), "Samsung S3C%s CAMIF",
+ ip_rev == S3C6410_CAMIF_IP_REV ? "6410" : "244X");
+ strscpy(md->bus_info, "platform", sizeof(md->bus_info));
+ md->hw_revision = ip_rev;
+
+ md->dev = camif->dev;
+
+ strscpy(v4l2_dev->name, "s3c-camif", sizeof(v4l2_dev->name));
+ v4l2_dev->mdev = md;
+
+ media_device_init(md);
+
+ ret = v4l2_device_register(camif->dev, v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static void camif_clk_put(struct camif_dev *camif)
+{
+ int i;
+
+ for (i = 0; i < CLK_MAX_NUM; i++) {
+ if (IS_ERR(camif->clock[i]))
+ continue;
+ clk_unprepare(camif->clock[i]);
+ clk_put(camif->clock[i]);
+ camif->clock[i] = ERR_PTR(-EINVAL);
+ }
+}
+
+static int camif_clk_get(struct camif_dev *camif)
+{
+ int ret, i;
+
+ for (i = 1; i < CLK_MAX_NUM; i++)
+ camif->clock[i] = ERR_PTR(-EINVAL);
+
+ for (i = 0; i < CLK_MAX_NUM; i++) {
+ camif->clock[i] = clk_get(camif->dev, camif_clocks[i]);
+ if (IS_ERR(camif->clock[i])) {
+ ret = PTR_ERR(camif->clock[i]);
+ goto err;
+ }
+ ret = clk_prepare(camif->clock[i]);
+ if (ret < 0) {
+ clk_put(camif->clock[i]);
+ camif->clock[i] = NULL;
+ goto err;
+ }
+ }
+ return 0;
+err:
+ camif_clk_put(camif);
+ dev_err(camif->dev, "failed to get clock: %s\n",
+ camif_clocks[i]);
+ return ret;
+}
+
+/*
+ * The CAMIF device has two relatively independent data processing paths
+ * that can source data from memory or the common camera input frontend.
+ * Register interrupts for each data processing path (camif_vp).
+ */
+static int camif_request_irqs(struct platform_device *pdev,
+ struct camif_dev *camif)
+{
+ int irq, ret, i;
+
+ for (i = 0; i < CAMIF_VP_NUM; i++) {
+ struct camif_vp *vp = &camif->vp[i];
+
+ init_waitqueue_head(&vp->irq_queue);
+
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(&pdev->dev, irq, s3c_camif_irq_handler,
+ 0, dev_name(&pdev->dev), vp);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to install IRQ: %d\n", ret);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int s3c_camif_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct s3c_camif_plat_data *pdata = dev->platform_data;
+ struct s3c_camif_drvdata *drvdata;
+ struct camif_dev *camif;
+ int ret = 0;
+
+ camif = devm_kzalloc(dev, sizeof(*camif), GFP_KERNEL);
+ if (!camif)
+ return -ENOMEM;
+
+ spin_lock_init(&camif->slock);
+ mutex_init(&camif->lock);
+
+ camif->dev = dev;
+
+ if (!pdata || !pdata->gpio_get || !pdata->gpio_put) {
+ dev_err(dev, "wrong platform data\n");
+ return -EINVAL;
+ }
+
+ camif->pdata = *pdata;
+ drvdata = (void *)platform_get_device_id(pdev)->driver_data;
+ camif->variant = drvdata->variant;
+
+ camif->io_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(camif->io_base))
+ return PTR_ERR(camif->io_base);
+
+ ret = camif_request_irqs(pdev, camif);
+ if (ret < 0)
+ return ret;
+
+ ret = pdata->gpio_get();
+ if (ret < 0)
+ return ret;
+
+ ret = s3c_camif_create_subdev(camif);
+ if (ret < 0)
+ goto err_sd;
+
+ ret = camif_clk_get(camif);
+ if (ret < 0)
+ goto err_clk;
+
+ platform_set_drvdata(pdev, camif);
+ clk_set_rate(camif->clock[CLK_CAM],
+ camif->pdata.sensor.clock_frequency);
+
+ dev_info(dev, "sensor clock frequency: %lu\n",
+ clk_get_rate(camif->clock[CLK_CAM]));
+ /*
+ * Set initial pixel format, resolution and crop rectangle.
+ * Must be done before a sensor subdev is registered as some
+ * settings are overrode with values from sensor subdev.
+ */
+ s3c_camif_set_defaults(camif);
+
+ pm_runtime_enable(dev);
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ goto err_disable;
+
+ ret = camif_media_dev_init(camif);
+ if (ret < 0)
+ goto err_pm;
+
+ ret = camif_register_sensor(camif);
+ if (ret < 0)
+ goto err_sens;
+
+ ret = v4l2_device_register_subdev(&camif->v4l2_dev, &camif->subdev);
+ if (ret < 0)
+ goto err_sens;
+
+ ret = v4l2_device_register_subdev_nodes(&camif->v4l2_dev);
+ if (ret < 0)
+ goto err_sens;
+
+ ret = camif_register_video_nodes(camif);
+ if (ret < 0)
+ goto err_sens;
+
+ ret = camif_create_media_links(camif);
+ if (ret < 0)
+ goto err_sens;
+
+ ret = media_device_register(&camif->media_dev);
+ if (ret < 0)
+ goto err_sens;
+
+ pm_runtime_put(dev);
+ return 0;
+
+err_sens:
+ v4l2_device_unregister(&camif->v4l2_dev);
+ media_device_unregister(&camif->media_dev);
+ media_device_cleanup(&camif->media_dev);
+ camif_unregister_media_entities(camif);
+err_pm:
+ pm_runtime_put(dev);
+err_disable:
+ pm_runtime_disable(dev);
+ camif_clk_put(camif);
+err_clk:
+ s3c_camif_unregister_subdev(camif);
+err_sd:
+ pdata->gpio_put();
+ return ret;
+}
+
+static void s3c_camif_remove(struct platform_device *pdev)
+{
+ struct camif_dev *camif = platform_get_drvdata(pdev);
+ struct s3c_camif_plat_data *pdata = &camif->pdata;
+
+ media_device_unregister(&camif->media_dev);
+ media_device_cleanup(&camif->media_dev);
+ camif_unregister_media_entities(camif);
+ v4l2_device_unregister(&camif->v4l2_dev);
+
+ pm_runtime_disable(&pdev->dev);
+ camif_clk_put(camif);
+ s3c_camif_unregister_subdev(camif);
+ pdata->gpio_put();
+}
+
+static int s3c_camif_runtime_resume(struct device *dev)
+{
+ struct camif_dev *camif = dev_get_drvdata(dev);
+
+ clk_enable(camif->clock[CLK_GATE]);
+ /* null op on s3c244x */
+ clk_enable(camif->clock[CLK_CAM]);
+ return 0;
+}
+
+static int s3c_camif_runtime_suspend(struct device *dev)
+{
+ struct camif_dev *camif = dev_get_drvdata(dev);
+
+ /* null op on s3c244x */
+ clk_disable(camif->clock[CLK_CAM]);
+
+ clk_disable(camif->clock[CLK_GATE]);
+ return 0;
+}
+
+static const struct s3c_camif_variant s3c244x_camif_variant = {
+ .vp_pix_limits = {
+ [VP_CODEC] = {
+ .max_out_width = 4096,
+ .max_sc_out_width = 2048,
+ .out_width_align = 16,
+ .min_out_width = 16,
+ .max_height = 4096,
+ },
+ [VP_PREVIEW] = {
+ .max_out_width = 640,
+ .max_sc_out_width = 640,
+ .out_width_align = 16,
+ .min_out_width = 16,
+ .max_height = 480,
+ }
+ },
+ .pix_limits = {
+ .win_hor_offset_align = 8,
+ },
+ .ip_revision = S3C244X_CAMIF_IP_REV,
+};
+
+static struct s3c_camif_drvdata s3c244x_camif_drvdata = {
+ .variant = &s3c244x_camif_variant,
+ .bus_clk_freq = 24000000UL,
+};
+
+static const struct s3c_camif_variant s3c6410_camif_variant = {
+ .vp_pix_limits = {
+ [VP_CODEC] = {
+ .max_out_width = 4096,
+ .max_sc_out_width = 2048,
+ .out_width_align = 16,
+ .min_out_width = 16,
+ .max_height = 4096,
+ },
+ [VP_PREVIEW] = {
+ .max_out_width = 4096,
+ .max_sc_out_width = 720,
+ .out_width_align = 16,
+ .min_out_width = 16,
+ .max_height = 4096,
+ }
+ },
+ .pix_limits = {
+ .win_hor_offset_align = 8,
+ },
+ .ip_revision = S3C6410_CAMIF_IP_REV,
+ .has_img_effect = 1,
+ .vp_offset = 0x20,
+};
+
+static struct s3c_camif_drvdata s3c6410_camif_drvdata = {
+ .variant = &s3c6410_camif_variant,
+ .bus_clk_freq = 133000000UL,
+};
+
+static const struct platform_device_id s3c_camif_driver_ids[] = {
+ {
+ .name = "s3c2440-camif",
+ .driver_data = (unsigned long)&s3c244x_camif_drvdata,
+ }, {
+ .name = "s3c6410-camif",
+ .driver_data = (unsigned long)&s3c6410_camif_drvdata,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, s3c_camif_driver_ids);
+
+static const struct dev_pm_ops s3c_camif_pm_ops = {
+ .runtime_suspend = s3c_camif_runtime_suspend,
+ .runtime_resume = s3c_camif_runtime_resume,
+};
+
+static struct platform_driver s3c_camif_driver = {
+ .probe = s3c_camif_probe,
+ .remove_new = s3c_camif_remove,
+ .id_table = s3c_camif_driver_ids,
+ .driver = {
+ .name = S3C_CAMIF_DRIVER_NAME,
+ .pm = &s3c_camif_pm_ops,
+ }
+};
+
+module_platform_driver(s3c_camif_driver);
+
+MODULE_AUTHOR("Sylwester Nawrocki <sylvester.nawrocki@gmail.com>");
+MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>");
+MODULE_DESCRIPTION("S3C24XX/S3C64XX SoC camera interface driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/s3c-camif/camif-core.h b/drivers/media/platform/samsung/s3c-camif/camif-core.h
new file mode 100644
index 0000000000..f3442e251b
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/camif-core.h
@@ -0,0 +1,397 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver
+ *
+ * Copyright (C) 2012 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
+ * Copyright (C) 2012 Tomasz Figa <tomasz.figa@gmail.com>
+*/
+
+#ifndef CAMIF_CORE_H_
+#define CAMIF_CORE_H_
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mediabus.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/drv-intf/s3c_camif.h>
+
+#define S3C_CAMIF_DRIVER_NAME "s3c-camif"
+#define CAMIF_REQ_BUFS_MIN 3
+#define CAMIF_MAX_OUT_BUFS 4
+#define CAMIF_MAX_PIX_WIDTH 4096
+#define CAMIF_MAX_PIX_HEIGHT 4096
+#define SCALER_MAX_RATIO 64
+#define CAMIF_DEF_WIDTH 640
+#define CAMIF_DEF_HEIGHT 480
+#define CAMIF_STOP_TIMEOUT 1500 /* ms */
+
+#define S3C244X_CAMIF_IP_REV 0x20 /* 2.0 */
+#define S3C2450_CAMIF_IP_REV 0x30 /* 3.0 - not implemented, not tested */
+#define S3C6400_CAMIF_IP_REV 0x31 /* 3.1 - not implemented, not tested */
+#define S3C6410_CAMIF_IP_REV 0x32 /* 3.2 */
+
+/* struct camif_vp::state */
+
+#define ST_VP_PENDING (1 << 0)
+#define ST_VP_RUNNING (1 << 1)
+#define ST_VP_STREAMING (1 << 2)
+#define ST_VP_SENSOR_STREAMING (1 << 3)
+
+#define ST_VP_ABORTING (1 << 4)
+#define ST_VP_OFF (1 << 5)
+#define ST_VP_LASTIRQ (1 << 6)
+
+#define ST_VP_CONFIG (1 << 8)
+
+#define CAMIF_SD_PAD_SINK 0
+#define CAMIF_SD_PAD_SOURCE_C 1
+#define CAMIF_SD_PAD_SOURCE_P 2
+#define CAMIF_SD_PADS_NUM 3
+
+enum img_fmt {
+ IMG_FMT_RGB565 = 0x0010,
+ IMG_FMT_RGB666,
+ IMG_FMT_XRGB8888,
+ IMG_FMT_YCBCR420 = 0x0020,
+ IMG_FMT_YCRCB420,
+ IMG_FMT_YCBCR422P,
+ IMG_FMT_YCBYCR422 = 0x0040,
+ IMG_FMT_YCRYCB422,
+ IMG_FMT_CBYCRY422,
+ IMG_FMT_CRYCBY422,
+};
+
+#define img_fmt_is_rgb(x) ((x) & 0x10)
+#define img_fmt_is_ycbcr(x) ((x) & 0x60)
+
+/* Possible values for struct camif_fmt::flags */
+#define FMT_FL_S3C24XX_CODEC (1 << 0)
+#define FMT_FL_S3C24XX_PREVIEW (1 << 1)
+#define FMT_FL_S3C64XX (1 << 2)
+
+/**
+ * struct camif_fmt - pixel format description
+ * @fourcc: fourcc code for this format, 0 if not applicable
+ * @color: a corresponding enum img_fmt
+ * @colplanes: number of physically contiguous data planes
+ * @flags: indicate for which SoCs revisions this format is valid
+ * @depth: bits per pixel (total)
+ * @ybpp: number of luminance bytes per pixel
+ */
+struct camif_fmt {
+ u32 fourcc;
+ u32 color;
+ u16 colplanes;
+ u16 flags;
+ u8 depth;
+ u8 ybpp;
+};
+
+/**
+ * struct camif_dma_offset - pixel offset information for DMA
+ * @initial: offset (in pixels) to first pixel
+ * @line: offset (in pixels) from end of line to start of next line
+ */
+struct camif_dma_offset {
+ int initial;
+ int line;
+};
+
+/**
+ * struct camif_frame - source/target frame properties
+ * @f_width: full pixel width
+ * @f_height: full pixel height
+ * @rect: crop/composition rectangle
+ * @dma_offset: DMA offset configuration
+ */
+struct camif_frame {
+ u16 f_width;
+ u16 f_height;
+ struct v4l2_rect rect;
+ struct camif_dma_offset dma_offset;
+};
+
+/* CAMIF clocks enumeration */
+enum {
+ CLK_GATE,
+ CLK_CAM,
+ CLK_MAX_NUM,
+};
+
+struct vp_pix_limits {
+ u16 max_out_width;
+ u16 max_sc_out_width;
+ u16 out_width_align;
+ u16 max_height;
+ u8 min_out_width;
+ u16 out_hor_offset_align;
+};
+
+struct camif_pix_limits {
+ u16 win_hor_offset_align;
+};
+
+/**
+ * struct s3c_camif_variant - CAMIF variant structure
+ * @vp_pix_limits: pixel limits for the codec and preview paths
+ * @pix_limits: pixel limits for the camera input interface
+ * @ip_revision: the CAMIF IP revision: 0x20 for s3c244x, 0x32 for s3c6410
+ * @has_img_effect: supports image effects
+ * @vp_offset: register offset
+ */
+struct s3c_camif_variant {
+ struct vp_pix_limits vp_pix_limits[2];
+ struct camif_pix_limits pix_limits;
+ u8 ip_revision;
+ u8 has_img_effect;
+ unsigned int vp_offset;
+};
+
+struct s3c_camif_drvdata {
+ const struct s3c_camif_variant *variant;
+ unsigned long bus_clk_freq;
+};
+
+struct camif_scaler {
+ u8 scaleup_h;
+ u8 scaleup_v;
+ u8 copy;
+ u8 enable;
+ u32 h_shift;
+ u32 v_shift;
+ u32 pre_h_ratio;
+ u32 pre_v_ratio;
+ u32 pre_dst_width;
+ u32 pre_dst_height;
+ u32 main_h_ratio;
+ u32 main_v_ratio;
+};
+
+struct camif_dev;
+
+/**
+ * struct camif_vp - CAMIF data processing path structure (codec/preview)
+ * @irq_queue: interrupt handling waitqueue
+ * @irq: interrupt number for this data path
+ * @camif: pointer to the camif structure
+ * @pad: media pad for the video node
+ * @vdev: video device
+ * @ctrl_handler: video node controls handler
+ * @owner: file handle that own the streaming
+ * @vb_queue: vb2 buffer queue
+ * @pending_buf_q: pending (empty) buffers queue head
+ * @active_buf_q: active (being written) buffers queue head
+ * @active_buffers: counter of buffer set up at the DMA engine
+ * @buf_index: identifier of a last empty buffer set up in H/W
+ * @frame_sequence: image frame sequence counter
+ * @reqbufs_count: the number of buffers requested
+ * @scaler: the scaler structure
+ * @out_fmt: pixel format at this video path output
+ * @payload: the output data frame payload size
+ * @out_frame: the output pixel resolution
+ * @state: the video path's state
+ * @fmt_flags: flags determining supported pixel formats
+ * @id: CAMIF id, 0 - codec, 1 - preview
+ * @rotation: current image rotation value
+ * @hflip: apply horizontal flip if set
+ * @vflip: apply vertical flip if set
+ * @offset: register offset
+ */
+struct camif_vp {
+ wait_queue_head_t irq_queue;
+ int irq;
+ struct camif_dev *camif;
+ struct media_pad pad;
+ struct video_device vdev;
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_fh *owner;
+ struct vb2_queue vb_queue;
+ struct list_head pending_buf_q;
+ struct list_head active_buf_q;
+ unsigned int active_buffers;
+ unsigned int buf_index;
+ unsigned int frame_sequence;
+ unsigned int reqbufs_count;
+ struct camif_scaler scaler;
+ const struct camif_fmt *out_fmt;
+ unsigned int payload;
+ struct camif_frame out_frame;
+ unsigned int state;
+ u16 fmt_flags;
+ u8 id;
+ u16 rotation;
+ u8 hflip;
+ u8 vflip;
+ unsigned int offset;
+};
+
+/* Video processing path enumeration */
+#define VP_CODEC 0
+#define VP_PREVIEW 1
+#define CAMIF_VP_NUM 2
+
+/**
+ * struct camif_dev - the CAMIF driver private data structure
+ * @media_dev: top-level media device structure
+ * @v4l2_dev: root v4l2_device
+ * @subdev: camera interface ("catchcam") subdev
+ * @mbus_fmt: camera input media bus format
+ * @camif_crop: camera input interface crop rectangle
+ * @pads: the camif subdev's media pads
+ * @stream_count: the camera interface streaming reference counter
+ * @sensor: image sensor data structure
+ * @m_pipeline: video entity pipeline description
+ * @ctrl_handler: v4l2 control handler (owned by @subdev)
+ * @ctrl_test_pattern: V4L2_CID_TEST_PATTERN control
+ * @ctrl_colorfx: V4L2_CID_COLORFX control
+ * @ctrl_colorfx_cbcr: V4L2_CID_COLORFX_CBCR control
+ * @test_pattern: test pattern
+ * @colorfx: color effect
+ * @colorfx_cb: Cb value for V4L2_COLORFX_SET_CBCR
+ * @colorfx_cr: Cr value for V4L2_COLORFX_SET_CBCR
+ * @vp: video path (DMA) description (codec/preview)
+ * @variant: variant information for this device
+ * @dev: pointer to the CAMIF device struct
+ * @pdata: a copy of the driver's platform data
+ * @clock: clocks required for the CAMIF operation
+ * @lock: mutex protecting this data structure
+ * @slock: spinlock protecting CAMIF registers
+ * @io_base: start address of the mmapped CAMIF registers
+ */
+struct camif_dev {
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev subdev;
+ struct v4l2_mbus_framefmt mbus_fmt;
+ struct v4l2_rect camif_crop;
+ struct media_pad pads[CAMIF_SD_PADS_NUM];
+ int stream_count;
+
+ struct cam_sensor {
+ struct v4l2_subdev *sd;
+ short power_count;
+ short stream_count;
+ } sensor;
+ struct media_pipeline *m_pipeline;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+ struct v4l2_ctrl *ctrl_test_pattern;
+ struct {
+ struct v4l2_ctrl *ctrl_colorfx;
+ struct v4l2_ctrl *ctrl_colorfx_cbcr;
+ };
+ u8 test_pattern;
+ u8 colorfx;
+ u8 colorfx_cb;
+ u8 colorfx_cr;
+
+ struct camif_vp vp[CAMIF_VP_NUM];
+
+ const struct s3c_camif_variant *variant;
+ struct device *dev;
+ struct s3c_camif_plat_data pdata;
+ struct clk *clock[CLK_MAX_NUM];
+ struct mutex lock;
+ spinlock_t slock;
+ void __iomem *io_base;
+};
+
+/**
+ * struct camif_addr - Y/Cb/Cr DMA start address structure
+ * @y: luminance plane dma address
+ * @cb: Cb plane dma address
+ * @cr: Cr plane dma address
+ */
+struct camif_addr {
+ dma_addr_t y;
+ dma_addr_t cb;
+ dma_addr_t cr;
+};
+
+/**
+ * struct camif_buffer - the camif video buffer structure
+ * @vb: vb2 buffer
+ * @list: list head for the buffers queue
+ * @paddr: DMA start addresses
+ * @index: an identifier of this buffer at the DMA engine
+ */
+struct camif_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+ struct camif_addr paddr;
+ unsigned int index;
+};
+
+const struct camif_fmt *s3c_camif_find_format(struct camif_vp *vp,
+ const u32 *pixelformat, int index);
+int s3c_camif_register_video_node(struct camif_dev *camif, int idx);
+void s3c_camif_unregister_video_node(struct camif_dev *camif, int idx);
+irqreturn_t s3c_camif_irq_handler(int irq, void *priv);
+int s3c_camif_create_subdev(struct camif_dev *camif);
+void s3c_camif_unregister_subdev(struct camif_dev *camif);
+int s3c_camif_set_defaults(struct camif_dev *camif);
+int s3c_camif_get_scaler_config(struct camif_vp *vp,
+ struct camif_scaler *scaler);
+
+static inline void camif_active_queue_add(struct camif_vp *vp,
+ struct camif_buffer *buf)
+{
+ list_add_tail(&buf->list, &vp->active_buf_q);
+ vp->active_buffers++;
+}
+
+static inline struct camif_buffer *camif_active_queue_pop(
+ struct camif_vp *vp)
+{
+ struct camif_buffer *buf = list_first_entry(&vp->active_buf_q,
+ struct camif_buffer, list);
+ list_del(&buf->list);
+ vp->active_buffers--;
+ return buf;
+}
+
+static inline struct camif_buffer *camif_active_queue_peek(
+ struct camif_vp *vp, int index)
+{
+ struct camif_buffer *tmp, *buf;
+
+ if (WARN_ON(list_empty(&vp->active_buf_q)))
+ return NULL;
+
+ list_for_each_entry_safe(buf, tmp, &vp->active_buf_q, list) {
+ if (buf->index == index) {
+ list_del(&buf->list);
+ vp->active_buffers--;
+ return buf;
+ }
+ }
+
+ return NULL;
+}
+
+static inline void camif_pending_queue_add(struct camif_vp *vp,
+ struct camif_buffer *buf)
+{
+ list_add_tail(&buf->list, &vp->pending_buf_q);
+}
+
+static inline struct camif_buffer *camif_pending_queue_pop(
+ struct camif_vp *vp)
+{
+ struct camif_buffer *buf = list_first_entry(&vp->pending_buf_q,
+ struct camif_buffer, list);
+ list_del(&buf->list);
+ return buf;
+}
+
+#endif /* CAMIF_CORE_H_ */
diff --git a/drivers/media/platform/samsung/s3c-camif/camif-regs.c b/drivers/media/platform/samsung/s3c-camif/camif-regs.c
new file mode 100644
index 0000000000..e80204f572
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/camif-regs.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Samsung s3c24xx/s3c64xx SoC CAMIF driver
+ *
+ * Copyright (C) 2012 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
+ * Copyright (C) 2012 Tomasz Figa <tomasz.figa@gmail.com>
+*/
+#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
+
+#include <linux/delay.h>
+#include "camif-regs.h"
+
+#define camif_write(_camif, _off, _val) writel(_val, (_camif)->io_base + (_off))
+#define camif_read(_camif, _off) readl((_camif)->io_base + (_off))
+
+void camif_hw_reset(struct camif_dev *camif)
+{
+ u32 cfg;
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CISRCFMT);
+ cfg |= CISRCFMT_ITU601_8BIT;
+ camif_write(camif, S3C_CAMIF_REG_CISRCFMT, cfg);
+
+ /* S/W reset */
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL);
+ cfg |= CIGCTRL_SWRST;
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV)
+ cfg |= CIGCTRL_IRQ_LEVEL;
+ camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg);
+ udelay(10);
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL);
+ cfg &= ~CIGCTRL_SWRST;
+ camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg);
+ udelay(10);
+}
+
+void camif_hw_clear_pending_irq(struct camif_vp *vp)
+{
+ u32 cfg = camif_read(vp->camif, S3C_CAMIF_REG_CIGCTRL);
+ cfg |= CIGCTRL_IRQ_CLR(vp->id);
+ camif_write(vp->camif, S3C_CAMIF_REG_CIGCTRL, cfg);
+}
+
+/*
+ * Sets video test pattern (off, color bar, horizontal or vertical gradient).
+ * External sensor pixel clock must be active for the test pattern to work.
+ */
+void camif_hw_set_test_pattern(struct camif_dev *camif, unsigned int pattern)
+{
+ u32 cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL);
+ cfg &= ~CIGCTRL_TESTPATTERN_MASK;
+ cfg |= (pattern << 27);
+ camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg);
+}
+
+void camif_hw_set_effect(struct camif_dev *camif, unsigned int effect,
+ unsigned int cr, unsigned int cb)
+{
+ static const struct v4l2_control colorfx[] = {
+ { V4L2_COLORFX_NONE, CIIMGEFF_FIN_BYPASS },
+ { V4L2_COLORFX_BW, CIIMGEFF_FIN_ARBITRARY },
+ { V4L2_COLORFX_SEPIA, CIIMGEFF_FIN_ARBITRARY },
+ { V4L2_COLORFX_NEGATIVE, CIIMGEFF_FIN_NEGATIVE },
+ { V4L2_COLORFX_ART_FREEZE, CIIMGEFF_FIN_ARTFREEZE },
+ { V4L2_COLORFX_EMBOSS, CIIMGEFF_FIN_EMBOSSING },
+ { V4L2_COLORFX_SILHOUETTE, CIIMGEFF_FIN_SILHOUETTE },
+ { V4L2_COLORFX_SET_CBCR, CIIMGEFF_FIN_ARBITRARY },
+ };
+ unsigned int i, cfg;
+
+ for (i = 0; i < ARRAY_SIZE(colorfx); i++)
+ if (colorfx[i].id == effect)
+ break;
+
+ if (i == ARRAY_SIZE(colorfx))
+ return;
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGEFF(camif->vp->offset));
+ /* Set effect */
+ cfg &= ~CIIMGEFF_FIN_MASK;
+ cfg |= colorfx[i].value;
+ /* Set both paths */
+ if (camif->variant->ip_revision >= S3C6400_CAMIF_IP_REV) {
+ if (effect == V4L2_COLORFX_NONE)
+ cfg &= ~CIIMGEFF_IE_ENABLE_MASK;
+ else
+ cfg |= CIIMGEFF_IE_ENABLE_MASK;
+ }
+ cfg &= ~CIIMGEFF_PAT_CBCR_MASK;
+ cfg |= cr | (cb << 13);
+ camif_write(camif, S3C_CAMIF_REG_CIIMGEFF(camif->vp->offset), cfg);
+}
+
+static const u32 src_pixfmt_map[8][2] = {
+ { MEDIA_BUS_FMT_YUYV8_2X8, CISRCFMT_ORDER422_YCBYCR },
+ { MEDIA_BUS_FMT_YVYU8_2X8, CISRCFMT_ORDER422_YCRYCB },
+ { MEDIA_BUS_FMT_UYVY8_2X8, CISRCFMT_ORDER422_CBYCRY },
+ { MEDIA_BUS_FMT_VYUY8_2X8, CISRCFMT_ORDER422_CRYCBY },
+};
+
+/* Set camera input pixel format and resolution */
+void camif_hw_set_source_format(struct camif_dev *camif)
+{
+ struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt;
+ int i;
+ u32 cfg;
+
+ for (i = ARRAY_SIZE(src_pixfmt_map) - 1; i >= 0; i--) {
+ if (src_pixfmt_map[i][0] == mf->code)
+ break;
+ }
+ if (i < 0) {
+ i = 0;
+ dev_err(camif->dev,
+ "Unsupported pixel code, falling back to %#08x\n",
+ src_pixfmt_map[i][0]);
+ }
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CISRCFMT);
+ cfg &= ~(CISRCFMT_ORDER422_MASK | CISRCFMT_SIZE_CAM_MASK);
+ cfg |= (mf->width << 16) | mf->height;
+ cfg |= src_pixfmt_map[i][1];
+ camif_write(camif, S3C_CAMIF_REG_CISRCFMT, cfg);
+}
+
+/* Set the camera host input window offsets (cropping) */
+void camif_hw_set_camera_crop(struct camif_dev *camif)
+{
+ struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt;
+ struct v4l2_rect *crop = &camif->camif_crop;
+ u32 hoff2, voff2;
+ u32 cfg;
+
+ /* Note: s3c244x requirement: left = f_width - rect.width / 2 */
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIWDOFST);
+ cfg &= ~(CIWDOFST_OFST_MASK | CIWDOFST_WINOFSEN);
+ cfg |= (crop->left << 16) | crop->top;
+ if (crop->left != 0 || crop->top != 0)
+ cfg |= CIWDOFST_WINOFSEN;
+ camif_write(camif, S3C_CAMIF_REG_CIWDOFST, cfg);
+
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) {
+ hoff2 = mf->width - crop->width - crop->left;
+ voff2 = mf->height - crop->height - crop->top;
+ cfg = (hoff2 << 16) | voff2;
+ camif_write(camif, S3C_CAMIF_REG_CIWDOFST2, cfg);
+ }
+}
+
+void camif_hw_clear_fifo_overflow(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ u32 cfg;
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIWDOFST);
+ if (vp->id == 0)
+ cfg |= (CIWDOFST_CLROVCOFIY | CIWDOFST_CLROVCOFICB |
+ CIWDOFST_CLROVCOFICR);
+ else
+ cfg |= (/* CIWDOFST_CLROVPRFIY | */ CIWDOFST_CLROVPRFICB |
+ CIWDOFST_CLROVPRFICR);
+ camif_write(camif, S3C_CAMIF_REG_CIWDOFST, cfg);
+}
+
+/* Set video bus signals polarity */
+void camif_hw_set_camera_bus(struct camif_dev *camif)
+{
+ unsigned int flags = camif->pdata.sensor.flags;
+
+ u32 cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL);
+
+ cfg &= ~(CIGCTRL_INVPOLPCLK | CIGCTRL_INVPOLVSYNC |
+ CIGCTRL_INVPOLHREF | CIGCTRL_INVPOLFIELD);
+
+ if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
+ cfg |= CIGCTRL_INVPOLPCLK;
+
+ if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+ cfg |= CIGCTRL_INVPOLVSYNC;
+ /*
+ * HREF is normally high during frame active data
+ * transmission and low during horizontal synchronization
+ * period. Thus HREF active high means HSYNC active low.
+ */
+ if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+ cfg |= CIGCTRL_INVPOLHREF; /* HREF active low */
+
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) {
+ if (flags & V4L2_MBUS_FIELD_EVEN_LOW)
+ cfg |= CIGCTRL_INVPOLFIELD;
+ cfg |= CIGCTRL_FIELDMODE;
+ }
+
+ pr_debug("Setting CIGCTRL to: %#x\n", cfg);
+
+ camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg);
+}
+
+void camif_hw_set_output_addr(struct camif_vp *vp,
+ struct camif_addr *paddr, int i)
+{
+ struct camif_dev *camif = vp->camif;
+
+ camif_write(camif, S3C_CAMIF_REG_CIYSA(vp->id, i), paddr->y);
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV
+ || vp->id == VP_CODEC) {
+ camif_write(camif, S3C_CAMIF_REG_CICBSA(vp->id, i),
+ paddr->cb);
+ camif_write(camif, S3C_CAMIF_REG_CICRSA(vp->id, i),
+ paddr->cr);
+ }
+
+ pr_debug("dst_buf[%d]: %pad, cb: %pad, cr: %pad\n",
+ i, &paddr->y, &paddr->cb, &paddr->cr);
+}
+
+static void camif_hw_set_out_dma_size(struct camif_vp *vp)
+{
+ struct camif_frame *frame = &vp->out_frame;
+ u32 cfg;
+
+ cfg = camif_read(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset));
+ cfg &= ~CITRGFMT_TARGETSIZE_MASK;
+ cfg |= (frame->f_width << 16) | frame->f_height;
+ camif_write(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg);
+}
+
+static void camif_get_dma_burst(u32 width, u32 ybpp, u32 *mburst, u32 *rburst)
+{
+ unsigned int nwords = width * ybpp / 4;
+ unsigned int div, rem;
+
+ if (WARN_ON(width < 8 || (width * ybpp) & 7))
+ return;
+
+ for (div = 16; div >= 2; div /= 2) {
+ if (nwords < div)
+ continue;
+
+ rem = nwords & (div - 1);
+ if (rem == 0) {
+ *mburst = div;
+ *rburst = div;
+ break;
+ }
+ if (rem == div / 2 || rem == div / 4) {
+ *mburst = div;
+ *rburst = rem;
+ break;
+ }
+ }
+}
+
+void camif_hw_set_output_dma(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_frame *frame = &vp->out_frame;
+ const struct camif_fmt *fmt = vp->out_fmt;
+ unsigned int ymburst = 0, yrburst = 0;
+ u32 cfg;
+
+ camif_hw_set_out_dma_size(vp);
+
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) {
+ struct camif_dma_offset *offset = &frame->dma_offset;
+ /* Set the input dma offsets. */
+ cfg = S3C_CISS_OFFS_INITIAL(offset->initial);
+ cfg |= S3C_CISS_OFFS_LINE(offset->line);
+ camif_write(camif, S3C_CAMIF_REG_CISSY(vp->id), cfg);
+ camif_write(camif, S3C_CAMIF_REG_CISSCB(vp->id), cfg);
+ camif_write(camif, S3C_CAMIF_REG_CISSCR(vp->id), cfg);
+ }
+
+ /* Configure DMA burst values */
+ camif_get_dma_burst(frame->rect.width, fmt->ybpp, &ymburst, &yrburst);
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CICTRL(vp->id, vp->offset));
+ cfg &= ~CICTRL_BURST_MASK;
+
+ cfg |= CICTRL_YBURST1(ymburst) | CICTRL_YBURST2(yrburst);
+ cfg |= CICTRL_CBURST1(ymburst / 2) | CICTRL_CBURST2(yrburst / 2);
+
+ camif_write(camif, S3C_CAMIF_REG_CICTRL(vp->id, vp->offset), cfg);
+
+ pr_debug("ymburst: %u, yrburst: %u\n", ymburst, yrburst);
+}
+
+void camif_hw_set_input_path(struct camif_vp *vp)
+{
+ u32 cfg = camif_read(vp->camif, S3C_CAMIF_REG_MSCTRL(vp->id));
+ cfg &= ~MSCTRL_SEL_DMA_CAM;
+ camif_write(vp->camif, S3C_CAMIF_REG_MSCTRL(vp->id), cfg);
+}
+
+void camif_hw_set_target_format(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_frame *frame = &vp->out_frame;
+ u32 cfg;
+
+ pr_debug("fw: %d, fh: %d color: %d\n", frame->f_width,
+ frame->f_height, vp->out_fmt->color);
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset));
+ cfg &= ~CITRGFMT_TARGETSIZE_MASK;
+
+ if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV) {
+ /* We currently support only YCbCr 4:2:2 at the camera input */
+ cfg |= CITRGFMT_IN422;
+ cfg &= ~CITRGFMT_OUT422;
+ if (vp->out_fmt->color == IMG_FMT_YCBCR422P)
+ cfg |= CITRGFMT_OUT422;
+ } else {
+ cfg &= ~CITRGFMT_OUTFORMAT_MASK;
+ switch (vp->out_fmt->color) {
+ case IMG_FMT_RGB565...IMG_FMT_XRGB8888:
+ cfg |= CITRGFMT_OUTFORMAT_RGB;
+ break;
+ case IMG_FMT_YCBCR420...IMG_FMT_YCRCB420:
+ cfg |= CITRGFMT_OUTFORMAT_YCBCR420;
+ break;
+ case IMG_FMT_YCBCR422P:
+ cfg |= CITRGFMT_OUTFORMAT_YCBCR422;
+ break;
+ case IMG_FMT_YCBYCR422...IMG_FMT_CRYCBY422:
+ cfg |= CITRGFMT_OUTFORMAT_YCBCR422I;
+ break;
+ }
+ }
+
+ /* Rotation is only supported by s3c64xx */
+ if (vp->rotation == 90 || vp->rotation == 270)
+ cfg |= (frame->f_height << 16) | frame->f_width;
+ else
+ cfg |= (frame->f_width << 16) | frame->f_height;
+ camif_write(camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg);
+
+ /* Target area, output pixel width * height */
+ cfg = camif_read(camif, S3C_CAMIF_REG_CITAREA(vp->id, vp->offset));
+ cfg &= ~CITAREA_MASK;
+ cfg |= (frame->f_width * frame->f_height);
+ camif_write(camif, S3C_CAMIF_REG_CITAREA(vp->id, vp->offset), cfg);
+}
+
+void camif_hw_set_flip(struct camif_vp *vp)
+{
+ u32 cfg = camif_read(vp->camif,
+ S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset));
+
+ cfg &= ~CITRGFMT_FLIP_MASK;
+
+ if (vp->hflip)
+ cfg |= CITRGFMT_FLIP_Y_MIRROR;
+ if (vp->vflip)
+ cfg |= CITRGFMT_FLIP_X_MIRROR;
+
+ camif_write(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg);
+}
+
+static void camif_hw_set_prescaler(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_scaler *sc = &vp->scaler;
+ u32 cfg, shfactor, addr;
+
+ addr = S3C_CAMIF_REG_CISCPRERATIO(vp->id, vp->offset);
+
+ shfactor = 10 - (sc->h_shift + sc->v_shift);
+ cfg = shfactor << 28;
+
+ cfg |= (sc->pre_h_ratio << 16) | sc->pre_v_ratio;
+ camif_write(camif, addr, cfg);
+
+ cfg = (sc->pre_dst_width << 16) | sc->pre_dst_height;
+ camif_write(camif, S3C_CAMIF_REG_CISCPREDST(vp->id, vp->offset), cfg);
+}
+
+static void camif_s3c244x_hw_set_scaler(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_scaler *scaler = &vp->scaler;
+ unsigned int color = vp->out_fmt->color;
+ u32 cfg;
+
+ camif_hw_set_prescaler(vp);
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset));
+
+ cfg &= ~(CISCCTRL_SCALEUP_MASK | CISCCTRL_SCALERBYPASS |
+ CISCCTRL_MAIN_RATIO_MASK | CIPRSCCTRL_RGB_FORMAT_24BIT);
+
+ if (scaler->enable) {
+ if (scaler->scaleup_h) {
+ if (vp->id == VP_CODEC)
+ cfg |= CISCCTRL_SCALEUP_H;
+ else
+ cfg |= CIPRSCCTRL_SCALEUP_H;
+ }
+ if (scaler->scaleup_v) {
+ if (vp->id == VP_CODEC)
+ cfg |= CISCCTRL_SCALEUP_V;
+ else
+ cfg |= CIPRSCCTRL_SCALEUP_V;
+ }
+ } else {
+ if (vp->id == VP_CODEC)
+ cfg |= CISCCTRL_SCALERBYPASS;
+ }
+
+ cfg |= ((scaler->main_h_ratio & 0x1ff) << 16);
+ cfg |= scaler->main_v_ratio & 0x1ff;
+
+ if (vp->id == VP_PREVIEW) {
+ if (color == IMG_FMT_XRGB8888)
+ cfg |= CIPRSCCTRL_RGB_FORMAT_24BIT;
+ cfg |= CIPRSCCTRL_SAMPLE;
+ }
+
+ camif_write(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset), cfg);
+
+ pr_debug("main: h_ratio: %#x, v_ratio: %#x",
+ scaler->main_h_ratio, scaler->main_v_ratio);
+}
+
+static void camif_s3c64xx_hw_set_scaler(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ struct camif_scaler *scaler = &vp->scaler;
+ unsigned int color = vp->out_fmt->color;
+ u32 cfg;
+
+ camif_hw_set_prescaler(vp);
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset));
+
+ cfg &= ~(CISCCTRL_CSCR2Y_WIDE | CISCCTRL_CSCY2R_WIDE
+ | CISCCTRL_SCALEUP_H | CISCCTRL_SCALEUP_V
+ | CISCCTRL_SCALERBYPASS | CISCCTRL_ONE2ONE
+ | CISCCTRL_INRGB_FMT_MASK | CISCCTRL_OUTRGB_FMT_MASK
+ | CISCCTRL_INTERLACE | CISCCTRL_EXTRGB_EXTENSION
+ | CISCCTRL_MAIN_RATIO_MASK);
+
+ cfg |= (CISCCTRL_CSCR2Y_WIDE | CISCCTRL_CSCY2R_WIDE);
+
+ if (!scaler->enable) {
+ cfg |= CISCCTRL_SCALERBYPASS;
+ } else {
+ if (scaler->scaleup_h)
+ cfg |= CISCCTRL_SCALEUP_H;
+ if (scaler->scaleup_v)
+ cfg |= CISCCTRL_SCALEUP_V;
+ if (scaler->copy)
+ cfg |= CISCCTRL_ONE2ONE;
+ }
+
+ switch (color) {
+ case IMG_FMT_RGB666:
+ cfg |= CISCCTRL_OUTRGB_FMT_RGB666;
+ break;
+ case IMG_FMT_XRGB8888:
+ cfg |= CISCCTRL_OUTRGB_FMT_RGB888;
+ break;
+ }
+
+ cfg |= (scaler->main_h_ratio & 0x1ff) << 16;
+ cfg |= scaler->main_v_ratio & 0x1ff;
+
+ camif_write(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset), cfg);
+
+ pr_debug("main: h_ratio: %#x, v_ratio: %#x",
+ scaler->main_h_ratio, scaler->main_v_ratio);
+}
+
+void camif_hw_set_scaler(struct camif_vp *vp)
+{
+ unsigned int ip_rev = vp->camif->variant->ip_revision;
+
+ if (ip_rev == S3C244X_CAMIF_IP_REV)
+ camif_s3c244x_hw_set_scaler(vp);
+ else
+ camif_s3c64xx_hw_set_scaler(vp);
+}
+
+void camif_hw_enable_scaler(struct camif_vp *vp, bool on)
+{
+ u32 addr = S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset);
+ u32 cfg;
+
+ cfg = camif_read(vp->camif, addr);
+ if (on)
+ cfg |= CISCCTRL_SCALERSTART;
+ else
+ cfg &= ~CISCCTRL_SCALERSTART;
+ camif_write(vp->camif, addr, cfg);
+}
+
+void camif_hw_set_lastirq(struct camif_vp *vp, int enable)
+{
+ u32 addr = S3C_CAMIF_REG_CICTRL(vp->id, vp->offset);
+ u32 cfg;
+
+ cfg = camif_read(vp->camif, addr);
+ if (enable)
+ cfg |= CICTRL_LASTIRQ_ENABLE;
+ else
+ cfg &= ~CICTRL_LASTIRQ_ENABLE;
+ camif_write(vp->camif, addr, cfg);
+}
+
+void camif_hw_enable_capture(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ u32 cfg;
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset));
+ camif->stream_count++;
+
+ if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV)
+ cfg |= CIIMGCPT_CPT_FREN_ENABLE(vp->id);
+
+ if (vp->scaler.enable)
+ cfg |= CIIMGCPT_IMGCPTEN_SC(vp->id);
+
+ if (camif->stream_count == 1)
+ cfg |= CIIMGCPT_IMGCPTEN;
+
+ camif_write(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset), cfg);
+
+ pr_debug("CIIMGCPT: %#x, camif->stream_count: %d\n",
+ cfg, camif->stream_count);
+}
+
+void camif_hw_disable_capture(struct camif_vp *vp)
+{
+ struct camif_dev *camif = vp->camif;
+ u32 cfg;
+
+ cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset));
+ cfg &= ~CIIMGCPT_IMGCPTEN_SC(vp->id);
+
+ if (WARN_ON(--(camif->stream_count) < 0))
+ camif->stream_count = 0;
+
+ if (camif->stream_count == 0)
+ cfg &= ~CIIMGCPT_IMGCPTEN;
+
+ pr_debug("CIIMGCPT: %#x, camif->stream_count: %d\n",
+ cfg, camif->stream_count);
+
+ camif_write(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset), cfg);
+}
+
+void camif_hw_dump_regs(struct camif_dev *camif, const char *label)
+{
+ static const struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ { S3C_CAMIF_REG_CISRCFMT, "CISRCFMT" },
+ { S3C_CAMIF_REG_CIWDOFST, "CIWDOFST" },
+ { S3C_CAMIF_REG_CIGCTRL, "CIGCTRL" },
+ { S3C_CAMIF_REG_CIWDOFST2, "CIWDOFST2" },
+ { S3C_CAMIF_REG_CIYSA(0, 0), "CICOYSA0" },
+ { S3C_CAMIF_REG_CICBSA(0, 0), "CICOCBSA0" },
+ { S3C_CAMIF_REG_CICRSA(0, 0), "CICOCRSA0" },
+ { S3C_CAMIF_REG_CIYSA(0, 1), "CICOYSA1" },
+ { S3C_CAMIF_REG_CICBSA(0, 1), "CICOCBSA1" },
+ { S3C_CAMIF_REG_CICRSA(0, 1), "CICOCRSA1" },
+ { S3C_CAMIF_REG_CIYSA(0, 2), "CICOYSA2" },
+ { S3C_CAMIF_REG_CICBSA(0, 2), "CICOCBSA2" },
+ { S3C_CAMIF_REG_CICRSA(0, 2), "CICOCRSA2" },
+ { S3C_CAMIF_REG_CIYSA(0, 3), "CICOYSA3" },
+ { S3C_CAMIF_REG_CICBSA(0, 3), "CICOCBSA3" },
+ { S3C_CAMIF_REG_CICRSA(0, 3), "CICOCRSA3" },
+ { S3C_CAMIF_REG_CIYSA(1, 0), "CIPRYSA0" },
+ { S3C_CAMIF_REG_CIYSA(1, 1), "CIPRYSA1" },
+ { S3C_CAMIF_REG_CIYSA(1, 2), "CIPRYSA2" },
+ { S3C_CAMIF_REG_CIYSA(1, 3), "CIPRYSA3" },
+ { S3C_CAMIF_REG_CITRGFMT(0, 0), "CICOTRGFMT" },
+ { S3C_CAMIF_REG_CITRGFMT(1, 0), "CIPRTRGFMT" },
+ { S3C_CAMIF_REG_CICTRL(0, 0), "CICOCTRL" },
+ { S3C_CAMIF_REG_CICTRL(1, 0), "CIPRCTRL" },
+ { S3C_CAMIF_REG_CISCPREDST(0, 0), "CICOSCPREDST" },
+ { S3C_CAMIF_REG_CISCPREDST(1, 0), "CIPRSCPREDST" },
+ { S3C_CAMIF_REG_CISCPRERATIO(0, 0), "CICOSCPRERATIO" },
+ { S3C_CAMIF_REG_CISCPRERATIO(1, 0), "CIPRSCPRERATIO" },
+ { S3C_CAMIF_REG_CISCCTRL(0, 0), "CICOSCCTRL" },
+ { S3C_CAMIF_REG_CISCCTRL(1, 0), "CIPRSCCTRL" },
+ { S3C_CAMIF_REG_CITAREA(0, 0), "CICOTAREA" },
+ { S3C_CAMIF_REG_CITAREA(1, 0), "CIPRTAREA" },
+ { S3C_CAMIF_REG_CISTATUS(0, 0), "CICOSTATUS" },
+ { S3C_CAMIF_REG_CISTATUS(1, 0), "CIPRSTATUS" },
+ { S3C_CAMIF_REG_CIIMGCPT(0), "CIIMGCPT" },
+ };
+ u32 i;
+
+ pr_info("--- %s ---\n", label);
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 cfg = readl(camif->io_base + registers[i].offset);
+ dev_info(camif->dev, "%s:\t0x%08x\n", registers[i].name, cfg);
+ }
+}
diff --git a/drivers/media/platform/samsung/s3c-camif/camif-regs.h b/drivers/media/platform/samsung/s3c-camif/camif-regs.h
new file mode 100644
index 0000000000..052948a7b6
--- /dev/null
+++ b/drivers/media/platform/samsung/s3c-camif/camif-regs.h
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register definition file for s3c24xx/s3c64xx SoC CAMIF driver
+ *
+ * Copyright (C) 2012 Sylwester Nawrocki <sylvester.nawrocki@gmail.com>
+ * Copyright (C) 2012 Tomasz Figa <tomasz.figa@gmail.com>
+*/
+
+#ifndef CAMIF_REGS_H_
+#define CAMIF_REGS_H_
+
+#include <linux/bitops.h>
+
+#include "camif-core.h"
+#include <media/drv-intf/s3c_camif.h>
+
+/*
+ * The id argument indicates the processing path:
+ * id = 0 - codec (FIMC C), 1 - preview (FIMC P).
+ */
+
+/* Camera input format */
+#define S3C_CAMIF_REG_CISRCFMT 0x00
+#define CISRCFMT_ITU601_8BIT BIT(31)
+#define CISRCFMT_ITU656_8BIT (0 << 31)
+#define CISRCFMT_ORDER422_YCBYCR (0 << 14)
+#define CISRCFMT_ORDER422_YCRYCB (1 << 14)
+#define CISRCFMT_ORDER422_CBYCRY (2 << 14)
+#define CISRCFMT_ORDER422_CRYCBY (3 << 14)
+#define CISRCFMT_ORDER422_MASK (3 << 14)
+#define CISRCFMT_SIZE_CAM_MASK (0x1fff << 16 | 0x1fff)
+
+/* Window offset */
+#define S3C_CAMIF_REG_CIWDOFST 0x04
+#define CIWDOFST_WINOFSEN BIT(31)
+#define CIWDOFST_CLROVCOFIY BIT(30)
+#define CIWDOFST_CLROVRLB_PR BIT(28)
+/* #define CIWDOFST_CLROVPRFIY BIT(27) */
+#define CIWDOFST_CLROVCOFICB BIT(15)
+#define CIWDOFST_CLROVCOFICR BIT(14)
+#define CIWDOFST_CLROVPRFICB BIT(13)
+#define CIWDOFST_CLROVPRFICR BIT(12)
+#define CIWDOFST_OFST_MASK (0x7ff << 16 | 0x7ff)
+
+/* Window offset 2 */
+#define S3C_CAMIF_REG_CIWDOFST2 0x14
+#define CIWDOFST2_OFST2_MASK (0xfff << 16 | 0xfff)
+
+/* Global control */
+#define S3C_CAMIF_REG_CIGCTRL 0x08
+#define CIGCTRL_SWRST BIT(31)
+#define CIGCTRL_CAMRST BIT(30)
+#define CIGCTRL_TESTPATTERN_NORMAL (0 << 27)
+#define CIGCTRL_TESTPATTERN_COLOR_BAR (1 << 27)
+#define CIGCTRL_TESTPATTERN_HOR_INC (2 << 27)
+#define CIGCTRL_TESTPATTERN_VER_INC (3 << 27)
+#define CIGCTRL_TESTPATTERN_MASK (3 << 27)
+#define CIGCTRL_INVPOLPCLK BIT(26)
+#define CIGCTRL_INVPOLVSYNC BIT(25)
+#define CIGCTRL_INVPOLHREF BIT(24)
+#define CIGCTRL_IRQ_OVFEN BIT(22)
+#define CIGCTRL_HREF_MASK BIT(21)
+#define CIGCTRL_IRQ_LEVEL BIT(20)
+/* IRQ_CLR_C, IRQ_CLR_P */
+#define CIGCTRL_IRQ_CLR(id) BIT(19 - (id))
+#define CIGCTRL_FIELDMODE BIT(2)
+#define CIGCTRL_INVPOLFIELD BIT(1)
+#define CIGCTRL_CAM_INTERLACE BIT(0)
+
+/* Y DMA output frame start address. n = 0..3. */
+#define S3C_CAMIF_REG_CIYSA(id, n) (0x18 + (id) * 0x54 + (n) * 4)
+/* Cb plane output DMA start address. n = 0..3. Only codec path. */
+#define S3C_CAMIF_REG_CICBSA(id, n) (0x28 + (id) * 0x54 + (n) * 4)
+/* Cr plane output DMA start address. n = 0..3. Only codec path. */
+#define S3C_CAMIF_REG_CICRSA(id, n) (0x38 + (id) * 0x54 + (n) * 4)
+
+/* CICOTRGFMT, CIPRTRGFMT - Target format */
+#define S3C_CAMIF_REG_CITRGFMT(id, _offs) (0x48 + (id) * (0x34 + (_offs)))
+#define CITRGFMT_IN422 BIT(31) /* only for s3c24xx */
+#define CITRGFMT_OUT422 BIT(30) /* only for s3c24xx */
+#define CITRGFMT_OUTFORMAT_YCBCR420 (0 << 29) /* only for s3c6410 */
+#define CITRGFMT_OUTFORMAT_YCBCR422 (1 << 29) /* only for s3c6410 */
+#define CITRGFMT_OUTFORMAT_YCBCR422I (2 << 29) /* only for s3c6410 */
+#define CITRGFMT_OUTFORMAT_RGB (3 << 29) /* only for s3c6410 */
+#define CITRGFMT_OUTFORMAT_MASK (3 << 29) /* only for s3c6410 */
+#define CITRGFMT_TARGETHSIZE(x) ((x) << 16)
+#define CITRGFMT_FLIP_NORMAL (0 << 14)
+#define CITRGFMT_FLIP_X_MIRROR (1 << 14)
+#define CITRGFMT_FLIP_Y_MIRROR (2 << 14)
+#define CITRGFMT_FLIP_180 (3 << 14)
+#define CITRGFMT_FLIP_MASK (3 << 14)
+/* Preview path only */
+#define CITRGFMT_ROT90_PR BIT(13)
+#define CITRGFMT_TARGETVSIZE(x) ((x) << 0)
+#define CITRGFMT_TARGETSIZE_MASK ((0x1fff << 16) | 0x1fff)
+
+/* CICOCTRL, CIPRCTRL. Output DMA control. */
+#define S3C_CAMIF_REG_CICTRL(id, _offs) (0x4c + (id) * (0x34 + (_offs)))
+#define CICTRL_BURST_MASK (0xfffff << 4)
+/* xBURSTn - 5-bits width */
+#define CICTRL_YBURST1(x) ((x) << 19)
+#define CICTRL_YBURST2(x) ((x) << 14)
+#define CICTRL_RGBBURST1(x) ((x) << 19)
+#define CICTRL_RGBBURST2(x) ((x) << 14)
+#define CICTRL_CBURST1(x) ((x) << 9)
+#define CICTRL_CBURST2(x) ((x) << 4)
+#define CICTRL_LASTIRQ_ENABLE BIT(2)
+#define CICTRL_ORDER422_MASK (3 << 0)
+
+/* CICOSCPRERATIO, CIPRSCPRERATIO. Pre-scaler control 1. */
+#define S3C_CAMIF_REG_CISCPRERATIO(id, _offs) (0x50 + (id) * (0x34 + (_offs)))
+
+/* CICOSCPREDST, CIPRSCPREDST. Pre-scaler control 2. */
+#define S3C_CAMIF_REG_CISCPREDST(id, _offs) (0x54 + (id) * (0x34 + (_offs)))
+
+/* CICOSCCTRL, CIPRSCCTRL. Main scaler control. */
+#define S3C_CAMIF_REG_CISCCTRL(id, _offs) (0x58 + (id) * (0x34 + (_offs)))
+#define CISCCTRL_SCALERBYPASS BIT(31)
+/* s3c244x preview path only, s3c64xx both */
+#define CIPRSCCTRL_SAMPLE BIT(31)
+/* 0 - 16-bit RGB, 1 - 24-bit RGB */
+#define CIPRSCCTRL_RGB_FORMAT_24BIT BIT(30) /* only for s3c244x */
+#define CIPRSCCTRL_SCALEUP_H BIT(29) /* only for s3c244x */
+#define CIPRSCCTRL_SCALEUP_V BIT(28) /* only for s3c244x */
+/* s3c64xx */
+#define CISCCTRL_SCALEUP_H BIT(30)
+#define CISCCTRL_SCALEUP_V BIT(29)
+#define CISCCTRL_SCALEUP_MASK (0x3 << 29)
+#define CISCCTRL_CSCR2Y_WIDE BIT(28)
+#define CISCCTRL_CSCY2R_WIDE BIT(27)
+#define CISCCTRL_LCDPATHEN_FIFO BIT(26)
+#define CISCCTRL_INTERLACE BIT(25)
+#define CISCCTRL_SCALERSTART BIT(15)
+#define CISCCTRL_INRGB_FMT_RGB565 (0 << 13)
+#define CISCCTRL_INRGB_FMT_RGB666 (1 << 13)
+#define CISCCTRL_INRGB_FMT_RGB888 (2 << 13)
+#define CISCCTRL_INRGB_FMT_MASK (3 << 13)
+#define CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11)
+#define CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11)
+#define CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11)
+#define CISCCTRL_OUTRGB_FMT_MASK (3 << 11)
+#define CISCCTRL_EXTRGB_EXTENSION BIT(10)
+#define CISCCTRL_ONE2ONE BIT(9)
+#define CISCCTRL_MAIN_RATIO_MASK (0x1ff << 16 | 0x1ff)
+
+/* CICOTAREA, CIPRTAREA. Target area for DMA (Hsize x Vsize). */
+#define S3C_CAMIF_REG_CITAREA(id, _offs) (0x5c + (id) * (0x34 + (_offs)))
+#define CITAREA_MASK 0xfffffff
+
+/* Codec (id = 0) or preview (id = 1) path status. */
+#define S3C_CAMIF_REG_CISTATUS(id, _offs) (0x64 + (id) * (0x34 + (_offs)))
+#define CISTATUS_OVFIY_STATUS BIT(31)
+#define CISTATUS_OVFICB_STATUS BIT(30)
+#define CISTATUS_OVFICR_STATUS BIT(29)
+#define CISTATUS_OVF_MASK (0x7 << 29)
+#define CIPRSTATUS_OVF_MASK (0x3 << 30)
+#define CISTATUS_VSYNC_STATUS BIT(28)
+#define CISTATUS_FRAMECNT_MASK (3 << 26)
+#define CISTATUS_FRAMECNT(__reg) (((__reg) >> 26) & 0x3)
+#define CISTATUS_WINOFSTEN_STATUS BIT(25)
+#define CISTATUS_IMGCPTEN_STATUS BIT(22)
+#define CISTATUS_IMGCPTENSC_STATUS BIT(21)
+#define CISTATUS_VSYNC_A_STATUS BIT(20)
+#define CISTATUS_FRAMEEND_STATUS BIT(19) /* 17 on s3c64xx */
+
+/* Image capture enable */
+#define S3C_CAMIF_REG_CIIMGCPT(_offs) (0xa0 + (_offs))
+#define CIIMGCPT_IMGCPTEN BIT(31)
+#define CIIMGCPT_IMGCPTEN_SC(id) BIT(30 - (id))
+/* Frame control: 1 - one-shot, 0 - free run */
+#define CIIMGCPT_CPT_FREN_ENABLE(id) BIT(25 - (id))
+#define CIIMGCPT_CPT_FRMOD_ENABLE (0 << 18)
+#define CIIMGCPT_CPT_FRMOD_CNT BIT(18)
+
+/* Capture sequence */
+#define S3C_CAMIF_REG_CICPTSEQ 0xc4
+
+/* Image effects */
+#define S3C_CAMIF_REG_CIIMGEFF(_offs) (0xb0 + (_offs))
+#define CIIMGEFF_IE_ENABLE(id) BIT(30 + (id))
+#define CIIMGEFF_IE_ENABLE_MASK (3 << 30)
+/* Image effect: 1 - after scaler, 0 - before scaler */
+#define CIIMGEFF_IE_AFTER_SC BIT(29)
+#define CIIMGEFF_FIN_MASK (7 << 26)
+#define CIIMGEFF_FIN_BYPASS (0 << 26)
+#define CIIMGEFF_FIN_ARBITRARY (1 << 26)
+#define CIIMGEFF_FIN_NEGATIVE (2 << 26)
+#define CIIMGEFF_FIN_ARTFREEZE (3 << 26)
+#define CIIMGEFF_FIN_EMBOSSING (4 << 26)
+#define CIIMGEFF_FIN_SILHOUETTE (5 << 26)
+#define CIIMGEFF_PAT_CBCR_MASK ((0xff << 13) | 0xff)
+#define CIIMGEFF_PAT_CB(x) ((x) << 13)
+#define CIIMGEFF_PAT_CR(x) (x)
+
+/* MSCOY0SA, MSPRY0SA. Y/Cb/Cr frame start address for input DMA. */
+#define S3C_CAMIF_REG_MSY0SA(id) (0xd4 + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCB0SA(id) (0xd8 + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCR0SA(id) (0xdc + ((id) * 0x2c))
+
+/* MSCOY0END, MSCOY0END. Y/Cb/Cr frame end address for input DMA. */
+#define S3C_CAMIF_REG_MSY0END(id) (0xe0 + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCB0END(id) (0xe4 + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCR0END(id) (0xe8 + ((id) * 0x2c))
+
+/* MSPRYOFF, MSPRYOFF. Y/Cb/Cr offset. n: 0 - codec, 1 - preview. */
+#define S3C_CAMIF_REG_MSYOFF(id) (0x118 + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCBOFF(id) (0x11c + ((id) * 0x2c))
+#define S3C_CAMIF_REG_MSCROFF(id) (0x120 + ((id) * 0x2c))
+
+/* Real input DMA data size. n = 0 - codec, 1 - preview. */
+#define S3C_CAMIF_REG_MSWIDTH(id) (0xf8 + (id) * 0x2c)
+#define AUTOLOAD_ENABLE BIT(31)
+#define ADDR_CH_DIS BIT(30)
+#define MSHEIGHT(x) (((x) & 0x3ff) << 16)
+#define MSWIDTH(x) ((x) & 0x3ff)
+
+/* Input DMA control. n = 0 - codec, 1 - preview */
+#define S3C_CAMIF_REG_MSCTRL(id) (0xfc + (id) * 0x2c)
+#define MSCTRL_ORDER422_M_YCBYCR (0 << 4)
+#define MSCTRL_ORDER422_M_YCRYCB (1 << 4)
+#define MSCTRL_ORDER422_M_CBYCRY (2 << 4)
+#define MSCTRL_ORDER422_M_CRYCBY (3 << 4)
+/* 0 - camera, 1 - DMA */
+#define MSCTRL_SEL_DMA_CAM BIT(3)
+#define MSCTRL_INFORMAT_M_YCBCR420 (0 << 1)
+#define MSCTRL_INFORMAT_M_YCBCR422 (1 << 1)
+#define MSCTRL_INFORMAT_M_YCBCR422I (2 << 1)
+#define MSCTRL_INFORMAT_M_RGB (3 << 1)
+#define MSCTRL_ENVID_M BIT(0)
+
+/* CICOSCOSY, CIPRSCOSY. Scan line Y/Cb/Cr offset. */
+#define S3C_CAMIF_REG_CISSY(id) (0x12c + (id) * 0x0c)
+#define S3C_CAMIF_REG_CISSCB(id) (0x130 + (id) * 0x0c)
+#define S3C_CAMIF_REG_CISSCR(id) (0x134 + (id) * 0x0c)
+#define S3C_CISS_OFFS_INITIAL(x) ((x) << 16)
+#define S3C_CISS_OFFS_LINE(x) ((x) << 0)
+
+/* ------------------------------------------------------------------ */
+
+void camif_hw_reset(struct camif_dev *camif);
+void camif_hw_clear_pending_irq(struct camif_vp *vp);
+void camif_hw_clear_fifo_overflow(struct camif_vp *vp);
+void camif_hw_set_lastirq(struct camif_vp *vp, int enable);
+void camif_hw_set_input_path(struct camif_vp *vp);
+void camif_hw_enable_scaler(struct camif_vp *vp, bool on);
+void camif_hw_enable_capture(struct camif_vp *vp);
+void camif_hw_disable_capture(struct camif_vp *vp);
+void camif_hw_set_camera_bus(struct camif_dev *camif);
+void camif_hw_set_source_format(struct camif_dev *camif);
+void camif_hw_set_camera_crop(struct camif_dev *camif);
+void camif_hw_set_scaler(struct camif_vp *vp);
+void camif_hw_set_flip(struct camif_vp *vp);
+void camif_hw_set_output_dma(struct camif_vp *vp);
+void camif_hw_set_target_format(struct camif_vp *vp);
+void camif_hw_set_test_pattern(struct camif_dev *camif, unsigned int pattern);
+void camif_hw_set_effect(struct camif_dev *camif, unsigned int effect,
+ unsigned int cr, unsigned int cb);
+void camif_hw_set_output_addr(struct camif_vp *vp, struct camif_addr *paddr,
+ int index);
+void camif_hw_dump_regs(struct camif_dev *camif, const char *label);
+
+static inline u32 camif_hw_get_status(struct camif_vp *vp)
+{
+ return readl(vp->camif->io_base + S3C_CAMIF_REG_CISTATUS(vp->id,
+ vp->offset));
+}
+
+#endif /* CAMIF_REGS_H_ */
diff --git a/drivers/media/platform/samsung/s5p-g2d/Kconfig b/drivers/media/platform/samsung/s5p-g2d/Kconfig
new file mode 100644
index 0000000000..28ab88fc2d
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/Kconfig
@@ -0,0 +1,11 @@
+config VIDEO_SAMSUNG_S5P_G2D
+ tristate "Samsung S5P and EXYNOS4 G2D 2d graphics accelerator driver"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on VIDEO_DEV
+ depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ help
+ This is a v4l2 driver for Samsung S5P and EXYNOS4 G2D
+ 2d graphics accelerator.
+
diff --git a/drivers/media/platform/samsung/s5p-g2d/Makefile b/drivers/media/platform/samsung/s5p-g2d/Makefile
new file mode 100644
index 0000000000..ad2c5bf66a
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+s5p-g2d-objs := g2d.o g2d-hw.o
+
+obj-$(CONFIG_VIDEO_SAMSUNG_S5P_G2D) += s5p-g2d.o
diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c b/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c
new file mode 100644
index 0000000000..b69d3fb125
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S5P G2D - 2D Graphics Accelerator Driver
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#include <linux/io.h>
+
+#include "g2d.h"
+#include "g2d-regs.h"
+
+#define w(x, a) writel((x), d->regs + (a))
+#define r(a) readl(d->regs + (a))
+
+/* g2d_reset clears all g2d registers */
+void g2d_reset(struct g2d_dev *d)
+{
+ w(1, SOFT_RESET_REG);
+}
+
+void g2d_set_src_size(struct g2d_dev *d, struct g2d_frame *f)
+{
+ u32 n;
+
+ w(0, SRC_SELECT_REG);
+ w(f->stride & 0xFFFF, SRC_STRIDE_REG);
+
+ n = f->o_height & 0xFFF;
+ n <<= 16;
+ n |= f->o_width & 0xFFF;
+ w(n, SRC_LEFT_TOP_REG);
+
+ n = f->bottom & 0xFFF;
+ n <<= 16;
+ n |= f->right & 0xFFF;
+ w(n, SRC_RIGHT_BOTTOM_REG);
+
+ w(f->fmt->hw, SRC_COLOR_MODE_REG);
+}
+
+void g2d_set_src_addr(struct g2d_dev *d, dma_addr_t a)
+{
+ w(a, SRC_BASE_ADDR_REG);
+}
+
+void g2d_set_dst_size(struct g2d_dev *d, struct g2d_frame *f)
+{
+ u32 n;
+
+ w(0, DST_SELECT_REG);
+ w(f->stride & 0xFFFF, DST_STRIDE_REG);
+
+ n = f->o_height & 0xFFF;
+ n <<= 16;
+ n |= f->o_width & 0xFFF;
+ w(n, DST_LEFT_TOP_REG);
+
+ n = f->bottom & 0xFFF;
+ n <<= 16;
+ n |= f->right & 0xFFF;
+ w(n, DST_RIGHT_BOTTOM_REG);
+
+ w(f->fmt->hw, DST_COLOR_MODE_REG);
+}
+
+void g2d_set_dst_addr(struct g2d_dev *d, dma_addr_t a)
+{
+ w(a, DST_BASE_ADDR_REG);
+}
+
+void g2d_set_rop4(struct g2d_dev *d, u32 r)
+{
+ w(r, ROP4_REG);
+}
+
+void g2d_set_flip(struct g2d_dev *d, u32 r)
+{
+ w(r, SRC_MSK_DIRECT_REG);
+}
+
+void g2d_set_v41_stretch(struct g2d_dev *d, struct g2d_frame *src,
+ struct g2d_frame *dst)
+{
+ w(DEFAULT_SCALE_MODE, SRC_SCALE_CTRL_REG);
+
+ /* inversed scaling factor: src is numerator */
+ w((src->c_width << 16) / dst->c_width, SRC_XSCALE_REG);
+ w((src->c_height << 16) / dst->c_height, SRC_YSCALE_REG);
+}
+
+void g2d_set_cmd(struct g2d_dev *d, u32 c)
+{
+ w(c, BITBLT_COMMAND_REG);
+}
+
+void g2d_start(struct g2d_dev *d)
+{
+ /* Clear cache */
+ if (d->variant->hw_rev == TYPE_G2D_3X)
+ w(0x7, CACHECTL_REG);
+
+ /* Enable interrupt */
+ w(1, INTEN_REG);
+ /* Start G2D engine */
+ w(1, BITBLT_START_REG);
+}
+
+void g2d_clear_int(struct g2d_dev *d)
+{
+ w(1, INTC_PEND_REG);
+}
diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h b/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h
new file mode 100644
index 0000000000..b2630c6133
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Samsung S5P G2D - 2D Graphics Accelerator Driver
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+/* General Registers */
+#define SOFT_RESET_REG 0x0000 /* Software reset reg */
+#define INTEN_REG 0x0004 /* Interrupt Enable reg */
+#define INTC_PEND_REG 0x000C /* Interrupt Control Pending reg */
+#define FIFO_STAT_REG 0x0010 /* Command FIFO Status reg */
+#define AXI_ID_MODE_REG 0x0014 /* AXI Read ID Mode reg */
+#define CACHECTL_REG 0x0018 /* Cache & Buffer clear reg */
+#define AXI_MODE_REG 0x001C /* AXI Mode reg */
+
+/* Command Registers */
+#define BITBLT_START_REG 0x0100 /* BitBLT Start reg */
+#define BITBLT_COMMAND_REG 0x0104 /* Command reg for BitBLT */
+
+/* Parameter Setting Registers (Rotate & Direction) */
+#define ROTATE_REG 0x0200 /* Rotation reg */
+#define SRC_MSK_DIRECT_REG 0x0204 /* Src and Mask Direction reg */
+#define DST_PAT_DIRECT_REG 0x0208 /* Dest and Pattern Direction reg */
+
+/* Parameter Setting Registers (Src) */
+#define SRC_SELECT_REG 0x0300 /* Src Image Selection reg */
+#define SRC_BASE_ADDR_REG 0x0304 /* Src Image Base Address reg */
+#define SRC_STRIDE_REG 0x0308 /* Src Stride reg */
+#define SRC_COLOR_MODE_REG 0x030C /* Src Image Color Mode reg */
+#define SRC_LEFT_TOP_REG 0x0310 /* Src Left Top Coordinate reg */
+#define SRC_RIGHT_BOTTOM_REG 0x0314 /* Src Right Bottom Coordinate reg */
+#define SRC_SCALE_CTRL_REG 0x0328 /* Src Scaling type select */
+#define SRC_XSCALE_REG 0x032c /* Src X Scaling ratio */
+#define SRC_YSCALE_REG 0x0330 /* Src Y Scaling ratio */
+
+/* Parameter Setting Registers (Dest) */
+#define DST_SELECT_REG 0x0400 /* Dest Image Selection reg */
+#define DST_BASE_ADDR_REG 0x0404 /* Dest Image Base Address reg */
+#define DST_STRIDE_REG 0x0408 /* Dest Stride reg */
+#define DST_COLOR_MODE_REG 0x040C /* Dest Image Color Mode reg */
+#define DST_LEFT_TOP_REG 0x0410 /* Dest Left Top Coordinate reg */
+#define DST_RIGHT_BOTTOM_REG 0x0414 /* Dest Right Bottom Coordinate reg */
+
+/* Parameter Setting Registers (Pattern) */
+#define PAT_BASE_ADDR_REG 0x0500 /* Pattern Image Base Address reg */
+#define PAT_SIZE_REG 0x0504 /* Pattern Image Size reg */
+#define PAT_COLOR_MODE_REG 0x0508 /* Pattern Image Color Mode reg */
+#define PAT_OFFSET_REG 0x050C /* Pattern Left Top Coordinate reg */
+#define PAT_STRIDE_REG 0x0510 /* Pattern Stride reg */
+
+/* Parameter Setting Registers (Mask) */
+#define MASK_BASE_ADDR_REG 0x0520 /* Mask Base Address reg */
+#define MASK_STRIDE_REG 0x0524 /* Mask Stride reg */
+
+/* Parameter Setting Registers (Clipping Window) */
+#define CW_LT_REG 0x0600 /* LeftTop coordinates of Clip Window */
+#define CW_RB_REG 0x0604 /* RightBottom coordinates of Clip
+ Window */
+
+/* Parameter Setting Registers (ROP & Alpha Setting) */
+#define THIRD_OPERAND_REG 0x0610 /* Third Operand Selection reg */
+#define ROP4_REG 0x0614 /* Raster Operation reg */
+#define ALPHA_REG 0x0618 /* Alpha value, Fading offset value */
+
+/* Parameter Setting Registers (Color) */
+#define FG_COLOR_REG 0x0700 /* Foreground Color reg */
+#define BG_COLOR_REG 0x0704 /* Background Color reg */
+#define BS_COLOR_REG 0x0708 /* Blue Screen Color reg */
+
+/* Parameter Setting Registers (Color Key) */
+#define SRC_COLORKEY_CTRL_REG 0x0710 /* Src Colorkey control reg */
+#define SRC_COLORKEY_DR_MIN_REG 0x0714 /* Src Colorkey Decision Reference
+ Min reg */
+#define SRC_COLORKEY_DR_MAX_REG 0x0718 /* Src Colorkey Decision Reference
+ Max reg */
+#define DST_COLORKEY_CTRL_REG 0x071C /* Dest Colorkey control reg */
+#define DST_COLORKEY_DR_MIN_REG 0x0720 /* Dest Colorkey Decision Reference
+ Min reg */
+#define DST_COLORKEY_DR_MAX_REG 0x0724 /* Dest Colorkey Decision Reference
+ Max reg */
+
+/* Color mode values */
+
+#define ORDER_XRGB 0
+#define ORDER_RGBX 1
+#define ORDER_XBGR 2
+#define ORDER_BGRX 3
+
+#define MODE_XRGB_8888 0
+#define MODE_ARGB_8888 1
+#define MODE_RGB_565 2
+#define MODE_XRGB_1555 3
+#define MODE_ARGB_1555 4
+#define MODE_XRGB_4444 5
+#define MODE_ARGB_4444 6
+#define MODE_PACKED_RGB_888 7
+
+#define COLOR_MODE(o, m) (((o) << 4) | (m))
+
+/* ROP4 operation values */
+#define ROP4_COPY 0xCCCC
+#define ROP4_INVERT 0x3333
+
+/* Hardware limits */
+#define MAX_WIDTH 8000
+#define MAX_HEIGHT 8000
+
+#define G2D_TIMEOUT 500
+
+#define DEFAULT_WIDTH 100
+#define DEFAULT_HEIGHT 100
+
+#define DEFAULT_SCALE_MODE (2 << 0)
+
+/* Command mode register values */
+#define CMD_V3_ENABLE_STRETCH (1 << 4)
diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d.c b/drivers/media/platform/samsung/s5p-g2d/g2d.c
new file mode 100644
index 0000000000..89aeba47ed
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/g2d.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S5P G2D - 2D Graphics Accelerator Driver
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+
+#include <linux/platform_device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "g2d.h"
+#include "g2d-regs.h"
+
+#define fh2ctx(__fh) container_of(__fh, struct g2d_ctx, fh)
+
+static struct g2d_fmt formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = 32,
+ .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_8888),
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565X,
+ .depth = 16,
+ .hw = COLOR_MODE(ORDER_XRGB, MODE_RGB_565),
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB555X,
+ .depth = 16,
+ .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_1555),
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB444,
+ .depth = 16,
+ .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_4444),
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .depth = 24,
+ .hw = COLOR_MODE(ORDER_XRGB, MODE_PACKED_RGB_888),
+ },
+};
+#define NUM_FORMATS ARRAY_SIZE(formats)
+
+static struct g2d_frame def_frame = {
+ .width = DEFAULT_WIDTH,
+ .height = DEFAULT_HEIGHT,
+ .c_width = DEFAULT_WIDTH,
+ .c_height = DEFAULT_HEIGHT,
+ .o_width = 0,
+ .o_height = 0,
+ .fmt = &formats[0],
+ .right = DEFAULT_WIDTH,
+ .bottom = DEFAULT_HEIGHT,
+};
+
+static struct g2d_fmt *find_fmt(struct v4l2_format *f)
+{
+ unsigned int i;
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].fourcc == f->fmt.pix.pixelformat)
+ return &formats[i];
+ }
+ return NULL;
+}
+
+
+static struct g2d_frame *get_frame(struct g2d_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ switch (type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ return &ctx->in;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ return &ctx->out;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static int g2d_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct g2d_ctx *ctx = vb2_get_drv_priv(vq);
+ struct g2d_frame *f = get_frame(ctx, vq->type);
+
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ sizes[0] = f->size;
+ *nplanes = 1;
+
+ if (*nbuffers == 0)
+ *nbuffers = 1;
+
+ return 0;
+}
+
+static int g2d_buf_prepare(struct vb2_buffer *vb)
+{
+ struct g2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct g2d_frame *f = get_frame(ctx, vb->vb2_queue->type);
+
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+ vb2_set_plane_payload(vb, 0, f->size);
+ return 0;
+}
+
+static void g2d_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct g2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static const struct vb2_ops g2d_qops = {
+ .queue_setup = g2d_queue_setup,
+ .buf_prepare = g2d_buf_prepare,
+ .buf_queue = g2d_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct g2d_ctx *ctx = priv;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &g2d_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->dev->mutex;
+ src_vq->dev = ctx->dev->v4l2_dev.dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR;
+ dst_vq->drv_priv = ctx;
+ dst_vq->ops = &g2d_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->dev->mutex;
+ dst_vq->dev = ctx->dev->v4l2_dev.dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+static int g2d_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct g2d_ctx *ctx = container_of(ctrl->handler, struct g2d_ctx,
+ ctrl_handler);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->dev->ctrl_lock, flags);
+ switch (ctrl->id) {
+ case V4L2_CID_COLORFX:
+ if (ctrl->val == V4L2_COLORFX_NEGATIVE)
+ ctx->rop = ROP4_INVERT;
+ else
+ ctx->rop = ROP4_COPY;
+ break;
+
+ case V4L2_CID_HFLIP:
+ ctx->flip = ctx->ctrl_hflip->val | (ctx->ctrl_vflip->val << 1);
+ break;
+
+ }
+ spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags);
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops g2d_ctrl_ops = {
+ .s_ctrl = g2d_s_ctrl,
+};
+
+static int g2d_setup_ctrls(struct g2d_ctx *ctx)
+{
+ struct g2d_dev *dev = ctx->dev;
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3);
+
+ ctx->ctrl_hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+ ctx->ctrl_vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ v4l2_ctrl_new_std_menu(
+ &ctx->ctrl_handler,
+ &g2d_ctrl_ops,
+ V4L2_CID_COLORFX,
+ V4L2_COLORFX_NEGATIVE,
+ ~((1 << V4L2_COLORFX_NONE) | (1 << V4L2_COLORFX_NEGATIVE)),
+ V4L2_COLORFX_NONE);
+
+ if (ctx->ctrl_handler.error) {
+ int err = ctx->ctrl_handler.error;
+ v4l2_err(&dev->v4l2_dev, "g2d_setup_ctrls failed\n");
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ return err;
+ }
+
+ v4l2_ctrl_cluster(2, &ctx->ctrl_hflip);
+
+ return 0;
+}
+
+static int g2d_open(struct file *file)
+{
+ struct g2d_dev *dev = video_drvdata(file);
+ struct g2d_ctx *ctx = NULL;
+ int ret = 0;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+ ctx->dev = dev;
+ /* Set default formats */
+ ctx->in = def_frame;
+ ctx->out = def_frame;
+
+ if (mutex_lock_interruptible(&dev->mutex)) {
+ kfree(ctx);
+ return -ERESTARTSYS;
+ }
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ mutex_unlock(&dev->mutex);
+ kfree(ctx);
+ return ret;
+ }
+ v4l2_fh_init(&ctx->fh, video_devdata(file));
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+
+ g2d_setup_ctrls(ctx);
+
+ /* Write the default values to the ctx struct */
+ v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+
+ ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+ mutex_unlock(&dev->mutex);
+
+ v4l2_info(&dev->v4l2_dev, "instance opened\n");
+ return 0;
+}
+
+static int g2d_release(struct file *file)
+{
+ struct g2d_dev *dev = video_drvdata(file);
+ struct g2d_ctx *ctx = fh2ctx(file->private_data);
+
+ mutex_lock(&dev->mutex);
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+ mutex_unlock(&dev->mutex);
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ v4l2_info(&dev->v4l2_dev, "instance closed\n");
+ return 0;
+}
+
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, G2D_NAME, sizeof(cap->driver));
+ strscpy(cap->card, G2D_NAME, sizeof(cap->card));
+ cap->bus_info[0] = 0;
+ return 0;
+}
+
+static int vidioc_enum_fmt(struct file *file, void *prv, struct v4l2_fmtdesc *f)
+{
+ if (f->index >= NUM_FORMATS)
+ return -EINVAL;
+ f->pixelformat = formats[f->index].fourcc;
+ return 0;
+}
+
+static int vidioc_g_fmt(struct file *file, void *prv, struct v4l2_format *f)
+{
+ struct g2d_ctx *ctx = prv;
+ struct vb2_queue *vq;
+ struct g2d_frame *frm;
+
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+ frm = get_frame(ctx, f->type);
+ if (IS_ERR(frm))
+ return PTR_ERR(frm);
+
+ f->fmt.pix.width = frm->width;
+ f->fmt.pix.height = frm->height;
+ f->fmt.pix.field = V4L2_FIELD_NONE;
+ f->fmt.pix.pixelformat = frm->fmt->fourcc;
+ f->fmt.pix.bytesperline = (frm->width * frm->fmt->depth) >> 3;
+ f->fmt.pix.sizeimage = frm->size;
+ return 0;
+}
+
+static int vidioc_try_fmt(struct file *file, void *prv, struct v4l2_format *f)
+{
+ struct g2d_fmt *fmt;
+ enum v4l2_field *field;
+
+ fmt = find_fmt(f);
+ if (!fmt)
+ return -EINVAL;
+
+ field = &f->fmt.pix.field;
+ if (*field == V4L2_FIELD_ANY)
+ *field = V4L2_FIELD_NONE;
+ else if (*field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ if (f->fmt.pix.width > MAX_WIDTH)
+ f->fmt.pix.width = MAX_WIDTH;
+ if (f->fmt.pix.height > MAX_HEIGHT)
+ f->fmt.pix.height = MAX_HEIGHT;
+
+ if (f->fmt.pix.width < 1)
+ f->fmt.pix.width = 1;
+ if (f->fmt.pix.height < 1)
+ f->fmt.pix.height = 1;
+
+ f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
+ f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+ return 0;
+}
+
+static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f)
+{
+ struct g2d_ctx *ctx = prv;
+ struct g2d_dev *dev = ctx->dev;
+ struct vb2_queue *vq;
+ struct g2d_frame *frm;
+ struct g2d_fmt *fmt;
+ int ret = 0;
+
+ /* Adjust all values accordingly to the hardware capabilities
+ * and chosen format. */
+ ret = vidioc_try_fmt(file, prv, f);
+ if (ret)
+ return ret;
+ vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+ if (vb2_is_busy(vq)) {
+ v4l2_err(&dev->v4l2_dev, "queue (%d) bust\n", f->type);
+ return -EBUSY;
+ }
+ frm = get_frame(ctx, f->type);
+ if (IS_ERR(frm))
+ return PTR_ERR(frm);
+ fmt = find_fmt(f);
+ if (!fmt)
+ return -EINVAL;
+ frm->width = f->fmt.pix.width;
+ frm->height = f->fmt.pix.height;
+ frm->size = f->fmt.pix.sizeimage;
+ /* Reset crop settings */
+ frm->o_width = 0;
+ frm->o_height = 0;
+ frm->c_width = frm->width;
+ frm->c_height = frm->height;
+ frm->right = frm->width;
+ frm->bottom = frm->height;
+ frm->fmt = fmt;
+ frm->stride = f->fmt.pix.bytesperline;
+ return 0;
+}
+
+static int vidioc_g_selection(struct file *file, void *prv,
+ struct v4l2_selection *s)
+{
+ struct g2d_ctx *ctx = prv;
+ struct g2d_frame *f;
+
+ f = get_frame(ctx, s->type);
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return -EINVAL;
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_COMPOSE:
+ s->r.left = f->o_height;
+ s->r.top = f->o_width;
+ s->r.width = f->c_width;
+ s->r.height = f->c_height;
+ break;
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = f->width;
+ s->r.height = f->height;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_try_selection(struct file *file, void *prv,
+ const struct v4l2_selection *s)
+{
+ struct g2d_ctx *ctx = prv;
+ struct g2d_dev *dev = ctx->dev;
+ struct g2d_frame *f;
+
+ f = get_frame(ctx, s->type);
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ if (s->target != V4L2_SEL_TGT_COMPOSE)
+ return -EINVAL;
+ } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ if (s->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+ }
+
+ if (s->r.top < 0 || s->r.left < 0) {
+ v4l2_err(&dev->v4l2_dev,
+ "doesn't support negative values for top & left\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vidioc_s_selection(struct file *file, void *prv,
+ struct v4l2_selection *s)
+{
+ struct g2d_ctx *ctx = prv;
+ struct g2d_frame *f;
+ int ret;
+
+ ret = vidioc_try_selection(file, prv, s);
+ if (ret)
+ return ret;
+ f = get_frame(ctx, s->type);
+ if (IS_ERR(f))
+ return PTR_ERR(f);
+
+ f->c_width = s->r.width;
+ f->c_height = s->r.height;
+ f->o_width = s->r.left;
+ f->o_height = s->r.top;
+ f->bottom = f->o_height + f->c_height;
+ f->right = f->o_width + f->c_width;
+ return 0;
+}
+
+static void device_run(void *prv)
+{
+ struct g2d_ctx *ctx = prv;
+ struct g2d_dev *dev = ctx->dev;
+ struct vb2_v4l2_buffer *src, *dst;
+ unsigned long flags;
+ u32 cmd = 0;
+
+ dev->curr = ctx;
+
+ src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+ clk_enable(dev->gate);
+ g2d_reset(dev);
+
+ spin_lock_irqsave(&dev->ctrl_lock, flags);
+
+ g2d_set_src_size(dev, &ctx->in);
+ g2d_set_src_addr(dev, vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0));
+
+ g2d_set_dst_size(dev, &ctx->out);
+ g2d_set_dst_addr(dev, vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0));
+
+ g2d_set_rop4(dev, ctx->rop);
+ g2d_set_flip(dev, ctx->flip);
+
+ if (ctx->in.c_width != ctx->out.c_width ||
+ ctx->in.c_height != ctx->out.c_height) {
+ if (dev->variant->hw_rev == TYPE_G2D_3X)
+ cmd |= CMD_V3_ENABLE_STRETCH;
+ else
+ g2d_set_v41_stretch(dev, &ctx->in, &ctx->out);
+ }
+
+ g2d_set_cmd(dev, cmd);
+ g2d_start(dev);
+
+ spin_unlock_irqrestore(&dev->ctrl_lock, flags);
+}
+
+static irqreturn_t g2d_isr(int irq, void *prv)
+{
+ struct g2d_dev *dev = prv;
+ struct g2d_ctx *ctx = dev->curr;
+ struct vb2_v4l2_buffer *src, *dst;
+
+ g2d_clear_int(dev);
+ clk_disable(dev->gate);
+
+ BUG_ON(ctx == NULL);
+
+ src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+ dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+ BUG_ON(src == NULL);
+ BUG_ON(dst == NULL);
+
+ dst->timecode = src->timecode;
+ dst->vb2_buf.timestamp = src->vb2_buf.timestamp;
+ dst->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ dst->flags |=
+ src->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+
+ v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE);
+ v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE);
+ v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
+
+ dev->curr = NULL;
+ return IRQ_HANDLED;
+}
+
+static const struct v4l2_file_operations g2d_fops = {
+ .owner = THIS_MODULE,
+ .open = g2d_open,
+ .release = g2d_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static const struct v4l2_ioctl_ops g2d_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
+ .vidioc_g_fmt_vid_cap = vidioc_g_fmt,
+ .vidioc_try_fmt_vid_cap = vidioc_try_fmt,
+ .vidioc_s_fmt_vid_cap = vidioc_s_fmt,
+
+ .vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
+ .vidioc_g_fmt_vid_out = vidioc_g_fmt,
+ .vidioc_try_fmt_vid_out = vidioc_try_fmt,
+ .vidioc_s_fmt_vid_out = vidioc_s_fmt,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+ .vidioc_g_selection = vidioc_g_selection,
+ .vidioc_s_selection = vidioc_s_selection,
+};
+
+static const struct video_device g2d_videodev = {
+ .name = G2D_NAME,
+ .fops = &g2d_fops,
+ .ioctl_ops = &g2d_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .vfl_dir = VFL_DIR_M2M,
+};
+
+static const struct v4l2_m2m_ops g2d_m2m_ops = {
+ .device_run = device_run,
+};
+
+static const struct of_device_id exynos_g2d_match[];
+
+static int g2d_probe(struct platform_device *pdev)
+{
+ struct g2d_dev *dev;
+ struct video_device *vfd;
+ const struct of_device_id *of_id;
+ int ret = 0;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->ctrl_lock);
+ mutex_init(&dev->mutex);
+ atomic_set(&dev->num_inst, 0);
+
+ dev->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dev->regs))
+ return PTR_ERR(dev->regs);
+
+ dev->clk = clk_get(&pdev->dev, "sclk_fimg2d");
+ if (IS_ERR(dev->clk)) {
+ dev_err(&pdev->dev, "failed to get g2d clock\n");
+ return -ENXIO;
+ }
+
+ ret = clk_prepare(dev->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to prepare g2d clock\n");
+ goto put_clk;
+ }
+
+ dev->gate = clk_get(&pdev->dev, "fimg2d");
+ if (IS_ERR(dev->gate)) {
+ dev_err(&pdev->dev, "failed to get g2d clock gate\n");
+ ret = -ENXIO;
+ goto unprep_clk;
+ }
+
+ ret = clk_prepare(dev->gate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to prepare g2d clock gate\n");
+ goto put_clk_gate;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto unprep_clk_gate;
+
+ dev->irq = ret;
+
+ ret = devm_request_irq(&pdev->dev, dev->irq, g2d_isr,
+ 0, pdev->name, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to install IRQ\n");
+ goto unprep_clk_gate;
+ }
+
+ vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32));
+
+ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+ if (ret)
+ goto unprep_clk_gate;
+ vfd = video_device_alloc();
+ if (!vfd) {
+ v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto unreg_v4l2_dev;
+ }
+ *vfd = g2d_videodev;
+ set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags);
+ vfd->lock = &dev->mutex;
+ vfd->v4l2_dev = &dev->v4l2_dev;
+ vfd->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING;
+
+ platform_set_drvdata(pdev, dev);
+ dev->m2m_dev = v4l2_m2m_init(&g2d_m2m_ops);
+ if (IS_ERR(dev->m2m_dev)) {
+ v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
+ ret = PTR_ERR(dev->m2m_dev);
+ goto rel_vdev;
+ }
+
+ def_frame.stride = (def_frame.width * def_frame.fmt->depth) >> 3;
+
+ of_id = of_match_node(exynos_g2d_match, pdev->dev.of_node);
+ if (!of_id) {
+ ret = -ENODEV;
+ goto free_m2m;
+ }
+ dev->variant = (struct g2d_variant *)of_id->data;
+
+ ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+ goto free_m2m;
+ }
+ video_set_drvdata(vfd, dev);
+ dev->vfd = vfd;
+ v4l2_info(&dev->v4l2_dev, "device registered as /dev/video%d\n",
+ vfd->num);
+
+ return 0;
+
+free_m2m:
+ v4l2_m2m_release(dev->m2m_dev);
+rel_vdev:
+ video_device_release(vfd);
+unreg_v4l2_dev:
+ v4l2_device_unregister(&dev->v4l2_dev);
+unprep_clk_gate:
+ clk_unprepare(dev->gate);
+put_clk_gate:
+ clk_put(dev->gate);
+unprep_clk:
+ clk_unprepare(dev->clk);
+put_clk:
+ clk_put(dev->clk);
+
+ return ret;
+}
+
+static void g2d_remove(struct platform_device *pdev)
+{
+ struct g2d_dev *dev = platform_get_drvdata(pdev);
+
+ v4l2_info(&dev->v4l2_dev, "Removing " G2D_NAME);
+ v4l2_m2m_release(dev->m2m_dev);
+ video_unregister_device(dev->vfd);
+ v4l2_device_unregister(&dev->v4l2_dev);
+ vb2_dma_contig_clear_max_seg_size(&pdev->dev);
+ clk_unprepare(dev->gate);
+ clk_put(dev->gate);
+ clk_unprepare(dev->clk);
+ clk_put(dev->clk);
+}
+
+static struct g2d_variant g2d_drvdata_v3x = {
+ .hw_rev = TYPE_G2D_3X, /* Revision 3.0 for S5PV210 and Exynos4210 */
+};
+
+static struct g2d_variant g2d_drvdata_v4x = {
+ .hw_rev = TYPE_G2D_4X, /* Revision 4.1 for Exynos4X12 and Exynos5 */
+};
+
+static const struct of_device_id exynos_g2d_match[] = {
+ {
+ .compatible = "samsung,s5pv210-g2d",
+ .data = &g2d_drvdata_v3x,
+ }, {
+ .compatible = "samsung,exynos4212-g2d",
+ .data = &g2d_drvdata_v4x,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_g2d_match);
+
+static struct platform_driver g2d_pdrv = {
+ .probe = g2d_probe,
+ .remove_new = g2d_remove,
+ .driver = {
+ .name = G2D_NAME,
+ .of_match_table = exynos_g2d_match,
+ },
+};
+
+module_platform_driver(g2d_pdrv);
+
+MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
+MODULE_DESCRIPTION("S5P G2D 2d graphics accelerator driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d.h b/drivers/media/platform/samsung/s5p-g2d/g2d.h
new file mode 100644
index 0000000000..c2309c1370
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-g2d/g2d.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Samsung S5P G2D - 2D Graphics Accelerator Driver
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#include <linux/platform_device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#define G2D_NAME "s5p-g2d"
+#define TYPE_G2D_3X 3
+#define TYPE_G2D_4X 4
+
+struct g2d_dev {
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct video_device *vfd;
+ struct mutex mutex;
+ spinlock_t ctrl_lock;
+ atomic_t num_inst;
+ void __iomem *regs;
+ struct clk *clk;
+ struct clk *gate;
+ struct g2d_ctx *curr;
+ struct g2d_variant *variant;
+ int irq;
+};
+
+struct g2d_frame {
+ /* Original dimensions */
+ u32 width;
+ u32 height;
+ /* Crop size */
+ u32 c_width;
+ u32 c_height;
+ /* Offset */
+ u32 o_width;
+ u32 o_height;
+ /* Image format */
+ struct g2d_fmt *fmt;
+ /* Variables that can calculated once and reused */
+ u32 stride;
+ u32 bottom;
+ u32 right;
+ u32 size;
+};
+
+struct g2d_ctx {
+ struct v4l2_fh fh;
+ struct g2d_dev *dev;
+ struct g2d_frame in;
+ struct g2d_frame out;
+ struct v4l2_ctrl *ctrl_hflip;
+ struct v4l2_ctrl *ctrl_vflip;
+ struct v4l2_ctrl_handler ctrl_handler;
+ u32 rop;
+ u32 flip;
+};
+
+struct g2d_fmt {
+ u32 fourcc;
+ int depth;
+ u32 hw;
+};
+
+struct g2d_variant {
+ unsigned short hw_rev;
+};
+
+void g2d_reset(struct g2d_dev *d);
+void g2d_set_src_size(struct g2d_dev *d, struct g2d_frame *f);
+void g2d_set_src_addr(struct g2d_dev *d, dma_addr_t a);
+void g2d_set_dst_size(struct g2d_dev *d, struct g2d_frame *f);
+void g2d_set_dst_addr(struct g2d_dev *d, dma_addr_t a);
+void g2d_start(struct g2d_dev *d);
+void g2d_clear_int(struct g2d_dev *d);
+void g2d_set_rop4(struct g2d_dev *d, u32 r);
+void g2d_set_flip(struct g2d_dev *d, u32 r);
+void g2d_set_v41_stretch(struct g2d_dev *d,
+ struct g2d_frame *src, struct g2d_frame *dst);
+void g2d_set_cmd(struct g2d_dev *d, u32 c);
diff --git a/drivers/media/platform/samsung/s5p-jpeg/Kconfig b/drivers/media/platform/samsung/s5p-jpeg/Kconfig
new file mode 100644
index 0000000000..11f6e99dec
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_SAMSUNG_S5P_JPEG
+ tristate "Samsung S5P/Exynos3250/Exynos4 JPEG codec driver"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on VIDEO_DEV
+ depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ help
+ This is a v4l2 driver for Samsung S5P, EXYNOS3250
+ and EXYNOS4 JPEG codec
diff --git a/drivers/media/platform/samsung/s5p-jpeg/Makefile b/drivers/media/platform/samsung/s5p-jpeg/Makefile
new file mode 100644
index 0000000000..8b0f92e27e
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+s5p-jpeg-objs := jpeg-core.o jpeg-hw-exynos3250.o jpeg-hw-exynos4.o jpeg-hw-s5p.o
+obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg.o
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c
new file mode 100644
index 0000000000..d2c4a0178b
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c
@@ -0,0 +1,3176 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c
+ *
+ * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gfp.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-rect.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "jpeg-core.h"
+#include "jpeg-hw-s5p.h"
+#include "jpeg-hw-exynos4.h"
+#include "jpeg-hw-exynos3250.h"
+#include "jpeg-regs.h"
+
+static struct s5p_jpeg_fmt sjpeg_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_JPEG,
+ .flags = SJPEG_FMT_FLAG_ENC_CAPTURE |
+ SJPEG_FMT_FLAG_DEC_OUTPUT |
+ SJPEG_FMT_FLAG_S5P |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_FLAG_EXYNOS4,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 4,
+ .v_align = 3,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_S5P |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 1,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 1,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 0,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565X,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = 16,
+ .colplanes = 1,
+ .h_align = 0,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_S5P |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = 32,
+ .colplanes = 1,
+ .h_align = 0,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = 32,
+ .colplanes = 1,
+ .h_align = 2,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV24,
+ .depth = 24,
+ .colplanes = 2,
+ .h_align = 0,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV42,
+ .depth = 24,
+ .colplanes = 2,
+ .h_align = 0,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV61,
+ .depth = 16,
+ .colplanes = 2,
+ .h_align = 1,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV16,
+ .depth = 16,
+ .colplanes = 2,
+ .h_align = 1,
+ .v_align = 0,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = 12,
+ .colplanes = 2,
+ .h_align = 1,
+ .v_align = 1,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = 12,
+ .colplanes = 2,
+ .h_align = 3,
+ .v_align = 3,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = 12,
+ .colplanes = 2,
+ .h_align = 4,
+ .v_align = 4,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_S5P |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .depth = 12,
+ .colplanes = 2,
+ .h_align = 3,
+ .v_align = 3,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21,
+ .depth = 12,
+ .colplanes = 2,
+ .h_align = 1,
+ .v_align = 1,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .depth = 12,
+ .colplanes = 3,
+ .h_align = 1,
+ .v_align = 1,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .depth = 12,
+ .colplanes = 3,
+ .h_align = 4,
+ .v_align = 4,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS3250 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .depth = 8,
+ .colplanes = 1,
+ .flags = SJPEG_FMT_FLAG_ENC_OUTPUT |
+ SJPEG_FMT_FLAG_DEC_CAPTURE |
+ SJPEG_FMT_FLAG_EXYNOS4 |
+ SJPEG_FMT_NON_RGB,
+ .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY,
+ },
+};
+#define SJPEG_NUM_FORMATS ARRAY_SIZE(sjpeg_formats)
+
+static const unsigned char qtbl_luminance[4][64] = {
+ {/*level 0 - high compression quality */
+ 20, 16, 25, 39, 50, 46, 62, 68,
+ 16, 18, 23, 38, 38, 53, 65, 68,
+ 25, 23, 31, 38, 53, 65, 68, 68,
+ 39, 38, 38, 53, 65, 68, 68, 68,
+ 50, 38, 53, 65, 68, 68, 68, 68,
+ 46, 53, 65, 68, 68, 68, 68, 68,
+ 62, 65, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68
+ },
+ {/* level 1 */
+ 16, 11, 11, 16, 23, 27, 31, 30,
+ 11, 12, 12, 15, 20, 23, 23, 30,
+ 11, 12, 13, 16, 23, 26, 35, 47,
+ 16, 15, 16, 23, 26, 37, 47, 64,
+ 23, 20, 23, 26, 39, 51, 64, 64,
+ 27, 23, 26, 37, 51, 64, 64, 64,
+ 31, 23, 35, 47, 64, 64, 64, 64,
+ 30, 30, 47, 64, 64, 64, 64, 64
+ },
+ {/* level 2 */
+ 12, 8, 8, 12, 17, 21, 24, 23,
+ 8, 9, 9, 11, 15, 19, 18, 23,
+ 8, 9, 10, 12, 19, 20, 27, 36,
+ 12, 11, 12, 21, 20, 28, 36, 53,
+ 17, 15, 19, 20, 30, 39, 51, 59,
+ 21, 19, 20, 28, 39, 51, 59, 59,
+ 24, 18, 27, 36, 51, 59, 59, 59,
+ 23, 23, 36, 53, 59, 59, 59, 59
+ },
+ {/* level 3 - low compression quality */
+ 8, 6, 6, 8, 12, 14, 16, 17,
+ 6, 6, 6, 8, 10, 13, 12, 15,
+ 6, 6, 7, 8, 13, 14, 18, 24,
+ 8, 8, 8, 14, 13, 19, 24, 35,
+ 12, 10, 13, 13, 20, 26, 34, 39,
+ 14, 13, 14, 19, 26, 34, 39, 39,
+ 16, 12, 18, 24, 34, 39, 39, 39,
+ 17, 15, 24, 35, 39, 39, 39, 39
+ }
+};
+
+static const unsigned char qtbl_chrominance[4][64] = {
+ {/*level 0 - high compression quality */
+ 21, 25, 32, 38, 54, 68, 68, 68,
+ 25, 28, 24, 38, 54, 68, 68, 68,
+ 32, 24, 32, 43, 66, 68, 68, 68,
+ 38, 38, 43, 53, 68, 68, 68, 68,
+ 54, 54, 66, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68
+ },
+ {/* level 1 */
+ 17, 15, 17, 21, 20, 26, 38, 48,
+ 15, 19, 18, 17, 20, 26, 35, 43,
+ 17, 18, 20, 22, 26, 30, 46, 53,
+ 21, 17, 22, 28, 30, 39, 53, 64,
+ 20, 20, 26, 30, 39, 48, 64, 64,
+ 26, 26, 30, 39, 48, 63, 64, 64,
+ 38, 35, 46, 53, 64, 64, 64, 64,
+ 48, 43, 53, 64, 64, 64, 64, 64
+ },
+ {/* level 2 */
+ 13, 11, 13, 16, 20, 20, 29, 37,
+ 11, 14, 14, 14, 16, 20, 26, 32,
+ 13, 14, 15, 17, 20, 23, 35, 40,
+ 16, 14, 17, 21, 23, 30, 40, 50,
+ 20, 16, 20, 23, 30, 37, 50, 59,
+ 20, 20, 23, 30, 37, 48, 59, 59,
+ 29, 26, 35, 40, 50, 59, 59, 59,
+ 37, 32, 40, 50, 59, 59, 59, 59
+ },
+ {/* level 3 - low compression quality */
+ 9, 8, 9, 11, 14, 17, 19, 24,
+ 8, 10, 9, 11, 14, 13, 17, 22,
+ 9, 9, 13, 14, 13, 15, 23, 26,
+ 11, 11, 14, 14, 15, 20, 26, 33,
+ 14, 14, 13, 15, 20, 24, 33, 39,
+ 17, 13, 15, 20, 24, 32, 39, 39,
+ 19, 17, 23, 26, 33, 39, 39, 39,
+ 24, 22, 26, 33, 39, 39, 39, 39
+ }
+};
+
+static const unsigned char hdctbl0[16] = {
+ 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const unsigned char hdctblg0[12] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb
+};
+static const unsigned char hactbl0[16] = {
+ 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
+};
+static const unsigned char hactblg0[162] = {
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
+ 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
+ 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+ 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
+ 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+ 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+ 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+ 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+ 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+ 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
+ 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+ 0xf9, 0xfa
+};
+
+/*
+ * Fourcc downgrade schema lookup tables for 422 and 420
+ * chroma subsampling - fourcc on each position maps on the
+ * fourcc from the table fourcc_to_dwngrd_schema_id which allows
+ * to get the most suitable fourcc counterpart for the given
+ * downgraded subsampling property.
+ */
+static const u32 subs422_fourcc_dwngrd_schema[] = {
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+};
+
+static const u32 subs420_fourcc_dwngrd_schema[] = {
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_GREY,
+ V4L2_PIX_FMT_GREY,
+ V4L2_PIX_FMT_GREY,
+ V4L2_PIX_FMT_GREY,
+};
+
+/*
+ * Lookup table for translation of a fourcc to the position
+ * of its downgraded counterpart in the *fourcc_dwngrd_schema
+ * tables.
+ */
+static const u32 fourcc_to_dwngrd_schema_id[] = {
+ V4L2_PIX_FMT_NV24,
+ V4L2_PIX_FMT_NV42,
+ V4L2_PIX_FMT_NV16,
+ V4L2_PIX_FMT_NV61,
+ V4L2_PIX_FMT_YUYV,
+ V4L2_PIX_FMT_YVYU,
+ V4L2_PIX_FMT_NV12,
+ V4L2_PIX_FMT_NV21,
+ V4L2_PIX_FMT_YUV420,
+ V4L2_PIX_FMT_GREY,
+};
+
+static int s5p_jpeg_get_dwngrd_sch_id_by_fourcc(u32 fourcc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fourcc_to_dwngrd_schema_id); ++i) {
+ if (fourcc_to_dwngrd_schema_id[i] == fourcc)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int s5p_jpeg_adjust_fourcc_to_subsampling(
+ enum v4l2_jpeg_chroma_subsampling subs,
+ u32 in_fourcc,
+ u32 *out_fourcc,
+ struct s5p_jpeg_ctx *ctx)
+{
+ int dwngrd_sch_id;
+
+ if (ctx->subsampling != V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY) {
+ dwngrd_sch_id =
+ s5p_jpeg_get_dwngrd_sch_id_by_fourcc(in_fourcc);
+ if (dwngrd_sch_id < 0)
+ return -EINVAL;
+ }
+
+ switch (ctx->subsampling) {
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY:
+ *out_fourcc = V4L2_PIX_FMT_GREY;
+ break;
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_420:
+ if (dwngrd_sch_id >
+ ARRAY_SIZE(subs420_fourcc_dwngrd_schema) - 1)
+ return -EINVAL;
+ *out_fourcc = subs420_fourcc_dwngrd_schema[dwngrd_sch_id];
+ break;
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_422:
+ if (dwngrd_sch_id >
+ ARRAY_SIZE(subs422_fourcc_dwngrd_schema) - 1)
+ return -EINVAL;
+ *out_fourcc = subs422_fourcc_dwngrd_schema[dwngrd_sch_id];
+ break;
+ default:
+ *out_fourcc = V4L2_PIX_FMT_GREY;
+ break;
+ }
+
+ return 0;
+}
+
+static int exynos4x12_decoded_subsampling[] = {
+ V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+};
+
+static int exynos3250_decoded_subsampling[] = {
+ V4L2_JPEG_CHROMA_SUBSAMPLING_444,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_422,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_420,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY,
+ -1,
+ -1,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_411,
+};
+
+static inline struct s5p_jpeg_ctx *ctrl_to_ctx(struct v4l2_ctrl *c)
+{
+ return container_of(c->handler, struct s5p_jpeg_ctx, ctrl_handler);
+}
+
+static inline struct s5p_jpeg_ctx *fh_to_ctx(struct v4l2_fh *fh)
+{
+ return container_of(fh, struct s5p_jpeg_ctx, fh);
+}
+
+static int s5p_jpeg_to_user_subsampling(struct s5p_jpeg_ctx *ctx)
+{
+ switch (ctx->jpeg->variant->version) {
+ case SJPEG_S5P:
+ WARN_ON(ctx->subsampling > 3);
+ if (ctx->subsampling > 2)
+ return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY;
+ return ctx->subsampling;
+ case SJPEG_EXYNOS3250:
+ case SJPEG_EXYNOS5420:
+ WARN_ON(ctx->subsampling > 6);
+ if (ctx->subsampling > 3)
+ return V4L2_JPEG_CHROMA_SUBSAMPLING_411;
+ return exynos3250_decoded_subsampling[ctx->subsampling];
+ case SJPEG_EXYNOS4:
+ WARN_ON(ctx->subsampling > 3);
+ if (ctx->subsampling > 2)
+ return V4L2_JPEG_CHROMA_SUBSAMPLING_420;
+ return exynos4x12_decoded_subsampling[ctx->subsampling];
+ case SJPEG_EXYNOS5433:
+ return ctx->subsampling; /* parsed from header */
+ default:
+ WARN_ON(ctx->subsampling > 3);
+ return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY;
+ }
+}
+
+static inline void s5p_jpeg_set_qtbl(void __iomem *regs,
+ const unsigned char *qtbl,
+ unsigned long tab, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ writel((unsigned int)qtbl[i], regs + tab + (i * 0x04));
+}
+
+static inline void s5p_jpeg_set_qtbl_lum(void __iomem *regs, int quality)
+{
+ /* this driver fills quantisation table 0 with data for luma */
+ s5p_jpeg_set_qtbl(regs, qtbl_luminance[quality],
+ S5P_JPG_QTBL_CONTENT(0),
+ ARRAY_SIZE(qtbl_luminance[quality]));
+}
+
+static inline void s5p_jpeg_set_qtbl_chr(void __iomem *regs, int quality)
+{
+ /* this driver fills quantisation table 1 with data for chroma */
+ s5p_jpeg_set_qtbl(regs, qtbl_chrominance[quality],
+ S5P_JPG_QTBL_CONTENT(1),
+ ARRAY_SIZE(qtbl_chrominance[quality]));
+}
+
+static inline void s5p_jpeg_set_htbl(void __iomem *regs,
+ const unsigned char *htbl,
+ unsigned long tab, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ writel((unsigned int)htbl[i], regs + tab + (i * 0x04));
+}
+
+static inline void s5p_jpeg_set_hdctbl(void __iomem *regs)
+{
+ /* this driver fills table 0 for this component */
+ s5p_jpeg_set_htbl(regs, hdctbl0, S5P_JPG_HDCTBL(0),
+ ARRAY_SIZE(hdctbl0));
+}
+
+static inline void s5p_jpeg_set_hdctblg(void __iomem *regs)
+{
+ /* this driver fills table 0 for this component */
+ s5p_jpeg_set_htbl(regs, hdctblg0, S5P_JPG_HDCTBLG(0),
+ ARRAY_SIZE(hdctblg0));
+}
+
+static inline void s5p_jpeg_set_hactbl(void __iomem *regs)
+{
+ /* this driver fills table 0 for this component */
+ s5p_jpeg_set_htbl(regs, hactbl0, S5P_JPG_HACTBL(0),
+ ARRAY_SIZE(hactbl0));
+}
+
+static inline void s5p_jpeg_set_hactblg(void __iomem *regs)
+{
+ /* this driver fills table 0 for this component */
+ s5p_jpeg_set_htbl(regs, hactblg0, S5P_JPG_HACTBLG(0),
+ ARRAY_SIZE(hactblg0));
+}
+
+static inline void exynos4_jpeg_set_tbl(void __iomem *regs,
+ const unsigned char *tbl,
+ unsigned long tab, int len)
+{
+ int i;
+ unsigned int dword;
+
+ for (i = 0; i < len; i += 4) {
+ dword = tbl[i] |
+ (tbl[i + 1] << 8) |
+ (tbl[i + 2] << 16) |
+ (tbl[i + 3] << 24);
+ writel(dword, regs + tab + i);
+ }
+}
+
+static inline void exynos4_jpeg_set_qtbl_lum(void __iomem *regs, int quality)
+{
+ /* this driver fills quantisation table 0 with data for luma */
+ exynos4_jpeg_set_tbl(regs, qtbl_luminance[quality],
+ EXYNOS4_QTBL_CONTENT(0),
+ ARRAY_SIZE(qtbl_luminance[quality]));
+}
+
+static inline void exynos4_jpeg_set_qtbl_chr(void __iomem *regs, int quality)
+{
+ /* this driver fills quantisation table 1 with data for chroma */
+ exynos4_jpeg_set_tbl(regs, qtbl_chrominance[quality],
+ EXYNOS4_QTBL_CONTENT(1),
+ ARRAY_SIZE(qtbl_chrominance[quality]));
+}
+
+static void exynos4_jpeg_set_huff_tbl(void __iomem *base)
+{
+ exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCLL,
+ ARRAY_SIZE(hdctbl0));
+ exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCCL,
+ ARRAY_SIZE(hdctbl0));
+ exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCLV,
+ ARRAY_SIZE(hdctblg0));
+ exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCCV,
+ ARRAY_SIZE(hdctblg0));
+ exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACLL,
+ ARRAY_SIZE(hactbl0));
+ exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACCL,
+ ARRAY_SIZE(hactbl0));
+ exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACLV,
+ ARRAY_SIZE(hactblg0));
+ exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACCV,
+ ARRAY_SIZE(hactblg0));
+}
+
+static inline int __exynos4_huff_tbl(int class, int id, bool lenval)
+{
+ /*
+ * class: 0 - DC, 1 - AC
+ * id: 0 - Y, 1 - Cb/Cr
+ */
+ if (class) {
+ if (id)
+ return lenval ? EXYNOS4_HUFF_TBL_HACCL :
+ EXYNOS4_HUFF_TBL_HACCV;
+ return lenval ? EXYNOS4_HUFF_TBL_HACLL : EXYNOS4_HUFF_TBL_HACLV;
+
+ }
+ /* class == 0 */
+ if (id)
+ return lenval ? EXYNOS4_HUFF_TBL_HDCCL : EXYNOS4_HUFF_TBL_HDCCV;
+
+ return lenval ? EXYNOS4_HUFF_TBL_HDCLL : EXYNOS4_HUFF_TBL_HDCLV;
+}
+
+static inline int exynos4_huff_tbl_len(int class, int id)
+{
+ return __exynos4_huff_tbl(class, id, true);
+}
+
+static inline int exynos4_huff_tbl_val(int class, int id)
+{
+ return __exynos4_huff_tbl(class, id, false);
+}
+
+static int get_byte(struct s5p_jpeg_buffer *buf);
+static int get_word_be(struct s5p_jpeg_buffer *buf, unsigned int *word);
+static void skip(struct s5p_jpeg_buffer *buf, long len);
+
+static void exynos4_jpeg_parse_decode_h_tbl(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ struct s5p_jpeg_buffer jpeg_buffer;
+ unsigned int word;
+ int c, x, components;
+
+ jpeg_buffer.size = 2; /* Ls */
+ jpeg_buffer.data =
+ (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + ctx->out_q.sos + 2;
+ jpeg_buffer.curr = 0;
+
+ word = 0;
+
+ if (get_word_be(&jpeg_buffer, &word))
+ return;
+ jpeg_buffer.size = (long)word - 2;
+ jpeg_buffer.data += 2;
+ jpeg_buffer.curr = 0;
+
+ components = get_byte(&jpeg_buffer);
+ if (components == -1)
+ return;
+ while (components--) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ x = get_byte(&jpeg_buffer);
+ if (x == -1)
+ return;
+ exynos4_jpeg_select_dec_h_tbl(jpeg->regs, c,
+ (((x >> 4) & 0x1) << 1) | (x & 0x1));
+ }
+
+}
+
+static void exynos4_jpeg_parse_huff_tbl(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ struct s5p_jpeg_buffer jpeg_buffer;
+ unsigned int word;
+ int c, i, n, j;
+
+ for (j = 0; j < ctx->out_q.dht.n; ++j) {
+ jpeg_buffer.size = ctx->out_q.dht.len[j];
+ jpeg_buffer.data = (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) +
+ ctx->out_q.dht.marker[j];
+ jpeg_buffer.curr = 0;
+
+ word = 0;
+ while (jpeg_buffer.curr < jpeg_buffer.size) {
+ char id, class;
+
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ id = c & 0xf;
+ class = (c >> 4) & 0xf;
+ n = 0;
+ for (i = 0; i < 16; ++i) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ word |= c << ((i % 4) * 8);
+ if ((i + 1) % 4 == 0) {
+ writel(word, jpeg->regs +
+ exynos4_huff_tbl_len(class, id) +
+ (i / 4) * 4);
+ word = 0;
+ }
+ n += c;
+ }
+ word = 0;
+ for (i = 0; i < n; ++i) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ word |= c << ((i % 4) * 8);
+ if ((i + 1) % 4 == 0) {
+ writel(word, jpeg->regs +
+ exynos4_huff_tbl_val(class, id) +
+ (i / 4) * 4);
+ word = 0;
+ }
+ }
+ if (i % 4) {
+ writel(word, jpeg->regs +
+ exynos4_huff_tbl_val(class, id) + (i / 4) * 4);
+ }
+ word = 0;
+ }
+ }
+}
+
+static void exynos4_jpeg_parse_decode_q_tbl(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ struct s5p_jpeg_buffer jpeg_buffer;
+ int c, x, components;
+
+ jpeg_buffer.size = ctx->out_q.sof_len;
+ jpeg_buffer.data =
+ (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + ctx->out_q.sof;
+ jpeg_buffer.curr = 0;
+
+ skip(&jpeg_buffer, 5); /* P, Y, X */
+ components = get_byte(&jpeg_buffer);
+ if (components == -1)
+ return;
+
+ exynos4_jpeg_set_dec_components(jpeg->regs, components);
+
+ while (components--) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ skip(&jpeg_buffer, 1);
+ x = get_byte(&jpeg_buffer);
+ if (x == -1)
+ return;
+ exynos4_jpeg_select_dec_q_tbl(jpeg->regs, c, x);
+ }
+}
+
+static void exynos4_jpeg_parse_q_tbl(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ struct s5p_jpeg_buffer jpeg_buffer;
+ unsigned int word;
+ int c, i, j;
+
+ for (j = 0; j < ctx->out_q.dqt.n; ++j) {
+ jpeg_buffer.size = ctx->out_q.dqt.len[j];
+ jpeg_buffer.data = (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) +
+ ctx->out_q.dqt.marker[j];
+ jpeg_buffer.curr = 0;
+
+ word = 0;
+ while (jpeg_buffer.size - jpeg_buffer.curr >= 65) {
+ char id;
+
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ id = c & 0xf;
+ /* nonzero means extended mode - not supported */
+ if ((c >> 4) & 0xf)
+ return;
+ for (i = 0; i < 64; ++i) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return;
+ word |= c << ((i % 4) * 8);
+ if ((i + 1) % 4 == 0) {
+ writel(word, jpeg->regs +
+ EXYNOS4_QTBL_CONTENT(id) + (i / 4) * 4);
+ word = 0;
+ }
+ }
+ word = 0;
+ }
+ }
+}
+
+/*
+ * ============================================================================
+ * Device file operations
+ * ============================================================================
+ */
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq);
+static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx,
+ __u32 pixelformat, unsigned int fmt_type);
+static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx);
+
+static int s5p_jpeg_open(struct file *file)
+{
+ struct s5p_jpeg *jpeg = video_drvdata(file);
+ struct video_device *vfd = video_devdata(file);
+ struct s5p_jpeg_ctx *ctx;
+ struct s5p_jpeg_fmt *out_fmt, *cap_fmt;
+ int ret = 0;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ if (mutex_lock_interruptible(&jpeg->lock)) {
+ ret = -ERESTARTSYS;
+ goto free;
+ }
+
+ v4l2_fh_init(&ctx->fh, vfd);
+ /* Use separate control handler per file handle */
+ ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+
+ ctx->jpeg = jpeg;
+ if (vfd == jpeg->vfd_encoder) {
+ ctx->mode = S5P_JPEG_ENCODE;
+ out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_RGB565,
+ FMT_TYPE_OUTPUT);
+ cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG,
+ FMT_TYPE_CAPTURE);
+ } else {
+ ctx->mode = S5P_JPEG_DECODE;
+ out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG,
+ FMT_TYPE_OUTPUT);
+ cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_YUYV,
+ FMT_TYPE_CAPTURE);
+ ctx->scale_factor = EXYNOS3250_DEC_SCALE_FACTOR_8_8;
+ }
+
+ ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, queue_init);
+ if (IS_ERR(ctx->fh.m2m_ctx)) {
+ ret = PTR_ERR(ctx->fh.m2m_ctx);
+ goto error;
+ }
+
+ ctx->out_q.fmt = out_fmt;
+ ctx->cap_q.fmt = cap_fmt;
+
+ ret = s5p_jpeg_controls_create(ctx);
+ if (ret < 0)
+ goto error;
+
+ mutex_unlock(&jpeg->lock);
+ return 0;
+
+error:
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ mutex_unlock(&jpeg->lock);
+free:
+ kfree(ctx);
+ return ret;
+}
+
+static int s5p_jpeg_release(struct file *file)
+{
+ struct s5p_jpeg *jpeg = video_drvdata(file);
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data);
+
+ mutex_lock(&jpeg->lock);
+ v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ mutex_unlock(&jpeg->lock);
+
+ return 0;
+}
+
+static const struct v4l2_file_operations s5p_jpeg_fops = {
+ .owner = THIS_MODULE,
+ .open = s5p_jpeg_open,
+ .release = s5p_jpeg_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+/*
+ * ============================================================================
+ * video ioctl operations
+ * ============================================================================
+ */
+
+static int get_byte(struct s5p_jpeg_buffer *buf)
+{
+ if (buf->curr >= buf->size)
+ return -1;
+
+ return ((unsigned char *)buf->data)[buf->curr++];
+}
+
+static int get_word_be(struct s5p_jpeg_buffer *buf, unsigned int *word)
+{
+ unsigned int temp;
+ int byte;
+
+ byte = get_byte(buf);
+ if (byte == -1)
+ return -1;
+ temp = byte << 8;
+ byte = get_byte(buf);
+ if (byte == -1)
+ return -1;
+ *word = (unsigned int)byte | temp;
+ return 0;
+}
+
+static void skip(struct s5p_jpeg_buffer *buf, long len)
+{
+ if (len <= 0)
+ return;
+
+ while (len--)
+ get_byte(buf);
+}
+
+static bool s5p_jpeg_subsampling_decode(struct s5p_jpeg_ctx *ctx,
+ unsigned int subsampling)
+{
+ unsigned int version;
+
+ switch (subsampling) {
+ case 0x11:
+ ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444;
+ break;
+ case 0x21:
+ ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422;
+ break;
+ case 0x22:
+ ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420;
+ break;
+ case 0x33:
+ ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY;
+ break;
+ case 0x41:
+ /*
+ * 4:1:1 subsampling only supported by 3250, 5420, and 5433
+ * variants
+ */
+ version = ctx->jpeg->variant->version;
+ if (version != SJPEG_EXYNOS3250 &&
+ version != SJPEG_EXYNOS5420 &&
+ version != SJPEG_EXYNOS5433)
+ return false;
+
+ ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_411;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result,
+ unsigned long buffer, unsigned long size,
+ struct s5p_jpeg_ctx *ctx)
+{
+ int c, components = 0, notfound, n_dht = 0, n_dqt = 0;
+ unsigned int height = 0, width = 0, word, subsampling = 0;
+ unsigned int sos = 0, sof = 0, sof_len = 0;
+ unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER];
+ unsigned int dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER];
+ long length;
+ struct s5p_jpeg_buffer jpeg_buffer;
+
+ jpeg_buffer.size = size;
+ jpeg_buffer.data = buffer;
+ jpeg_buffer.curr = 0;
+
+ notfound = 1;
+ while (notfound || !sos) {
+ c = get_byte(&jpeg_buffer);
+ if (c == -1)
+ return false;
+ if (c != 0xff)
+ continue;
+ do
+ c = get_byte(&jpeg_buffer);
+ while (c == 0xff);
+ if (c == -1)
+ return false;
+ if (c == 0)
+ continue;
+ length = 0;
+ switch (c) {
+ /* JPEG_MARKER_SOF0: baseline JPEG */
+ case JPEG_MARKER_SOF0:
+ if (get_word_be(&jpeg_buffer, &word))
+ break;
+ length = (long)word - 2;
+ if (!length)
+ return false;
+ sof = jpeg_buffer.curr; /* after 0xffc0 */
+ sof_len = length;
+ if (get_byte(&jpeg_buffer) == -1)
+ break;
+ if (get_word_be(&jpeg_buffer, &height))
+ break;
+ if (get_word_be(&jpeg_buffer, &width))
+ break;
+ components = get_byte(&jpeg_buffer);
+ if (components == -1)
+ break;
+
+ if (components == 1) {
+ subsampling = 0x33;
+ } else {
+ skip(&jpeg_buffer, 1);
+ subsampling = get_byte(&jpeg_buffer);
+ skip(&jpeg_buffer, 1);
+ }
+ if (components > 3)
+ return false;
+ skip(&jpeg_buffer, components * 2);
+ notfound = 0;
+ break;
+
+ case JPEG_MARKER_DQT:
+ if (get_word_be(&jpeg_buffer, &word))
+ break;
+ length = (long)word - 2;
+ if (!length)
+ return false;
+ if (n_dqt >= S5P_JPEG_MAX_MARKER)
+ return false;
+ dqt[n_dqt] = jpeg_buffer.curr; /* after 0xffdb */
+ dqt_len[n_dqt++] = length;
+ skip(&jpeg_buffer, length);
+ break;
+
+ case JPEG_MARKER_DHT:
+ if (get_word_be(&jpeg_buffer, &word))
+ break;
+ length = (long)word - 2;
+ if (!length)
+ return false;
+ if (n_dht >= S5P_JPEG_MAX_MARKER)
+ return false;
+ dht[n_dht] = jpeg_buffer.curr; /* after 0xffc4 */
+ dht_len[n_dht++] = length;
+ skip(&jpeg_buffer, length);
+ break;
+
+ case JPEG_MARKER_SOS:
+ sos = jpeg_buffer.curr - 2; /* 0xffda */
+ break;
+
+ /* skip payload-less markers */
+ case JPEG_MARKER_RST ... JPEG_MARKER_RST + 7:
+ case JPEG_MARKER_SOI:
+ case JPEG_MARKER_EOI:
+ case JPEG_MARKER_TEM:
+ break;
+
+ /* skip uninteresting payload markers */
+ default:
+ if (get_word_be(&jpeg_buffer, &word))
+ break;
+ length = (long)word - 2;
+ skip(&jpeg_buffer, length);
+ break;
+ }
+ }
+
+ if (notfound || !sos || !s5p_jpeg_subsampling_decode(ctx, subsampling))
+ return false;
+
+ result->w = width;
+ result->h = height;
+ result->sos = sos;
+ result->dht.n = n_dht;
+ while (n_dht--) {
+ result->dht.marker[n_dht] = dht[n_dht];
+ result->dht.len[n_dht] = dht_len[n_dht];
+ }
+ result->dqt.n = n_dqt;
+ while (n_dqt--) {
+ result->dqt.marker[n_dqt] = dqt[n_dqt];
+ result->dqt.len[n_dqt] = dqt_len[n_dqt];
+ }
+ result->sof = sof;
+ result->sof_len = sof_len;
+
+ return true;
+}
+
+static int s5p_jpeg_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ strscpy(cap->driver, S5P_JPEG_M2M_NAME,
+ sizeof(cap->driver));
+ strscpy(cap->card, S5P_JPEG_M2M_NAME " encoder",
+ sizeof(cap->card));
+ } else {
+ strscpy(cap->driver, S5P_JPEG_M2M_NAME,
+ sizeof(cap->driver));
+ strscpy(cap->card, S5P_JPEG_M2M_NAME " decoder",
+ sizeof(cap->card));
+ }
+ return 0;
+}
+
+static int enum_fmt(struct s5p_jpeg_ctx *ctx,
+ struct s5p_jpeg_fmt *sjpeg_formats, int n,
+ struct v4l2_fmtdesc *f, u32 type)
+{
+ int i, num = 0;
+ unsigned int fmt_ver_flag = ctx->jpeg->variant->fmt_ver_flag;
+
+ for (i = 0; i < n; ++i) {
+ if (sjpeg_formats[i].flags & type &&
+ sjpeg_formats[i].flags & fmt_ver_flag) {
+ /* index-th format of type type found ? */
+ if (num == f->index)
+ break;
+ /* Correct type but haven't reached our index yet,
+ * just increment per-type index
+ */
+ ++num;
+ }
+ }
+
+ /* Format not found */
+ if (i >= n)
+ return -EINVAL;
+
+ f->pixelformat = sjpeg_formats[i].fourcc;
+
+ return 0;
+}
+
+static int s5p_jpeg_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+
+ if (ctx->mode == S5P_JPEG_ENCODE)
+ return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f,
+ SJPEG_FMT_FLAG_ENC_CAPTURE);
+
+ return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f,
+ SJPEG_FMT_FLAG_DEC_CAPTURE);
+}
+
+static int s5p_jpeg_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+
+ if (ctx->mode == S5P_JPEG_ENCODE)
+ return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f,
+ SJPEG_FMT_FLAG_ENC_OUTPUT);
+
+ return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f,
+ SJPEG_FMT_FLAG_DEC_OUTPUT);
+}
+
+static struct s5p_jpeg_q_data *get_q_data(struct s5p_jpeg_ctx *ctx,
+ enum v4l2_buf_type type)
+{
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+ return &ctx->out_q;
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return &ctx->cap_q;
+
+ return NULL;
+}
+
+static int s5p_jpeg_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct vb2_queue *vq;
+ struct s5p_jpeg_q_data *q_data = NULL;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct s5p_jpeg_ctx *ct = fh_to_ctx(priv);
+
+ vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+ ct->mode == S5P_JPEG_DECODE && !ct->hdr_parsed)
+ return -EINVAL;
+ q_data = get_q_data(ct, f->type);
+ BUG_ON(q_data == NULL);
+
+ pix->width = q_data->w;
+ pix->height = q_data->h;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = q_data->fmt->fourcc;
+ pix->bytesperline = 0;
+ if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) {
+ u32 bpl = q_data->w;
+
+ if (q_data->fmt->colplanes == 1)
+ bpl = (bpl * q_data->fmt->depth) >> 3;
+ pix->bytesperline = bpl;
+ }
+ pix->sizeimage = q_data->size;
+
+ return 0;
+}
+
+static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx,
+ u32 pixelformat, unsigned int fmt_type)
+{
+ unsigned int k, fmt_flag;
+
+ if (ctx->mode == S5P_JPEG_ENCODE)
+ fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ?
+ SJPEG_FMT_FLAG_ENC_OUTPUT :
+ SJPEG_FMT_FLAG_ENC_CAPTURE;
+ else
+ fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ?
+ SJPEG_FMT_FLAG_DEC_OUTPUT :
+ SJPEG_FMT_FLAG_DEC_CAPTURE;
+
+ for (k = 0; k < ARRAY_SIZE(sjpeg_formats); k++) {
+ struct s5p_jpeg_fmt *fmt = &sjpeg_formats[k];
+
+ if (fmt->fourcc == pixelformat &&
+ fmt->flags & fmt_flag &&
+ fmt->flags & ctx->jpeg->variant->fmt_ver_flag) {
+ return fmt;
+ }
+ }
+
+ return NULL;
+}
+
+static void jpeg_bound_align_image(struct s5p_jpeg_ctx *ctx,
+ u32 *w, unsigned int wmin, unsigned int wmax,
+ unsigned int walign,
+ u32 *h, unsigned int hmin, unsigned int hmax,
+ unsigned int halign)
+{
+ int width, height, w_step, h_step;
+
+ width = *w;
+ height = *h;
+
+ w_step = 1 << walign;
+ h_step = 1 << halign;
+
+ if (ctx->jpeg->variant->hw3250_compat) {
+ /*
+ * Rightmost and bottommost pixels are cropped by the
+ * Exynos3250/compatible JPEG IP for RGB formats, for the
+ * specific width and height values respectively. This
+ * assignment will result in v4l_bound_align_image returning
+ * dimensions reduced by 1 for the aforementioned cases.
+ */
+ if (w_step == 4 && ((width & 3) == 1)) {
+ wmax = width;
+ hmax = height;
+ }
+ }
+
+ v4l_bound_align_image(w, wmin, wmax, walign, h, hmin, hmax, halign, 0);
+
+ if (*w < width && (*w + w_step) < wmax)
+ *w += w_step;
+ if (*h < height && (*h + h_step) < hmax)
+ *h += h_step;
+}
+
+static int vidioc_try_fmt(struct v4l2_format *f, struct s5p_jpeg_fmt *fmt,
+ struct s5p_jpeg_ctx *ctx, int q_type)
+{
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ if (pix->field == V4L2_FIELD_ANY)
+ pix->field = V4L2_FIELD_NONE;
+ else if (pix->field != V4L2_FIELD_NONE)
+ return -EINVAL;
+
+ /* V4L2 specification suggests the driver corrects the format struct
+ * if any of the dimensions is unsupported
+ */
+ if (q_type == FMT_TYPE_OUTPUT)
+ jpeg_bound_align_image(ctx, &pix->width, S5P_JPEG_MIN_WIDTH,
+ S5P_JPEG_MAX_WIDTH, 0,
+ &pix->height, S5P_JPEG_MIN_HEIGHT,
+ S5P_JPEG_MAX_HEIGHT, 0);
+ else
+ jpeg_bound_align_image(ctx, &pix->width, S5P_JPEG_MIN_WIDTH,
+ S5P_JPEG_MAX_WIDTH, fmt->h_align,
+ &pix->height, S5P_JPEG_MIN_HEIGHT,
+ S5P_JPEG_MAX_HEIGHT, fmt->v_align);
+
+ if (fmt->fourcc == V4L2_PIX_FMT_JPEG) {
+ if (pix->sizeimage <= 0)
+ pix->sizeimage = PAGE_SIZE;
+ pix->bytesperline = 0;
+ } else {
+ u32 bpl = pix->bytesperline;
+
+ if (fmt->colplanes > 1 && bpl < pix->width)
+ bpl = pix->width; /* planar */
+
+ if (fmt->colplanes == 1 && /* packed */
+ (bpl << 3) / fmt->depth < pix->width)
+ bpl = (pix->width * fmt->depth) >> 3;
+
+ pix->bytesperline = bpl;
+ pix->sizeimage = (pix->width * pix->height * fmt->depth) >> 3;
+ }
+
+ return 0;
+}
+
+static int s5p_jpeg_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct s5p_jpeg_fmt *fmt;
+ int ret;
+
+ fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat,
+ FMT_TYPE_CAPTURE);
+ if (!fmt) {
+ v4l2_err(&ctx->jpeg->v4l2_dev,
+ "Fourcc format (0x%08x) invalid.\n",
+ f->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+
+ if (!ctx->jpeg->variant->hw_ex4_compat || ctx->mode != S5P_JPEG_DECODE)
+ goto exit;
+
+ /*
+ * The exynos4x12 device requires resulting YUV image
+ * subsampling not to be lower than the input jpeg subsampling.
+ * If this requirement is not met then downgrade the requested
+ * capture format to the one with subsampling equal to the input jpeg.
+ */
+ if ((fmt->flags & SJPEG_FMT_NON_RGB) &&
+ (fmt->subsampling < ctx->subsampling)) {
+ ret = s5p_jpeg_adjust_fourcc_to_subsampling(ctx->subsampling,
+ fmt->fourcc,
+ &pix->pixelformat,
+ ctx);
+ if (ret < 0)
+ pix->pixelformat = V4L2_PIX_FMT_GREY;
+
+ fmt = s5p_jpeg_find_format(ctx, pix->pixelformat,
+ FMT_TYPE_CAPTURE);
+ }
+
+ /*
+ * Decompression of a JPEG file with 4:2:0 subsampling and odd
+ * width to the YUV 4:2:0 compliant formats produces a raw image
+ * with broken luma component. Adjust capture format to RGB565
+ * in such a case.
+ */
+ if (ctx->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420 &&
+ (ctx->out_q.w & 1) &&
+ (pix->pixelformat == V4L2_PIX_FMT_NV12 ||
+ pix->pixelformat == V4L2_PIX_FMT_NV21 ||
+ pix->pixelformat == V4L2_PIX_FMT_YUV420)) {
+ pix->pixelformat = V4L2_PIX_FMT_RGB565;
+ fmt = s5p_jpeg_find_format(ctx, pix->pixelformat,
+ FMT_TYPE_CAPTURE);
+ }
+
+exit:
+ return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_CAPTURE);
+}
+
+static int s5p_jpeg_try_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+ struct s5p_jpeg_fmt *fmt;
+
+ fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat,
+ FMT_TYPE_OUTPUT);
+ if (!fmt) {
+ v4l2_err(&ctx->jpeg->v4l2_dev,
+ "Fourcc format (0x%08x) invalid.\n",
+ f->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+
+ return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_OUTPUT);
+}
+
+static int exynos4_jpeg_get_output_buffer_size(struct s5p_jpeg_ctx *ctx,
+ struct v4l2_format *f,
+ int fmt_depth)
+{
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ u32 pix_fmt = f->fmt.pix.pixelformat;
+ int w = pix->width, h = pix->height, wh_align;
+ int padding = 0;
+
+ if (pix_fmt == V4L2_PIX_FMT_RGB32 ||
+ pix_fmt == V4L2_PIX_FMT_RGB565 ||
+ pix_fmt == V4L2_PIX_FMT_NV24 ||
+ pix_fmt == V4L2_PIX_FMT_NV42 ||
+ pix_fmt == V4L2_PIX_FMT_NV12 ||
+ pix_fmt == V4L2_PIX_FMT_NV21 ||
+ pix_fmt == V4L2_PIX_FMT_YUV420)
+ wh_align = 4;
+ else
+ wh_align = 1;
+
+ jpeg_bound_align_image(ctx, &w, S5P_JPEG_MIN_WIDTH,
+ S5P_JPEG_MAX_WIDTH, wh_align,
+ &h, S5P_JPEG_MIN_HEIGHT,
+ S5P_JPEG_MAX_HEIGHT, wh_align);
+
+ if (ctx->jpeg->variant->version == SJPEG_EXYNOS4)
+ padding = PAGE_SIZE;
+
+ return (w * h * fmt_depth >> 3) + padding;
+}
+
+static int exynos3250_jpeg_try_downscale(struct s5p_jpeg_ctx *ctx,
+ struct v4l2_rect *r);
+
+static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f)
+{
+ struct vb2_queue *vq;
+ struct s5p_jpeg_q_data *q_data = NULL;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct v4l2_ctrl *ctrl_subs;
+ struct v4l2_rect scale_rect;
+ unsigned int f_type;
+
+ vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ q_data = get_q_data(ct, f->type);
+ BUG_ON(q_data == NULL);
+
+ if (vb2_is_busy(vq)) {
+ v4l2_err(&ct->jpeg->v4l2_dev, "%s queue busy\n", __func__);
+ return -EBUSY;
+ }
+
+ f_type = V4L2_TYPE_IS_OUTPUT(f->type) ?
+ FMT_TYPE_OUTPUT : FMT_TYPE_CAPTURE;
+
+ q_data->fmt = s5p_jpeg_find_format(ct, pix->pixelformat, f_type);
+ if (ct->mode == S5P_JPEG_ENCODE ||
+ (ct->mode == S5P_JPEG_DECODE &&
+ q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG)) {
+ q_data->w = pix->width;
+ q_data->h = pix->height;
+ }
+ if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) {
+ /*
+ * During encoding Exynos4x12 SoCs access wider memory area
+ * than it results from Image_x and Image_y values written to
+ * the JPEG_IMAGE_SIZE register. In order to avoid sysmmu
+ * page fault calculate proper buffer size in such a case.
+ */
+ if (ct->jpeg->variant->hw_ex4_compat &&
+ f_type == FMT_TYPE_OUTPUT && ct->mode == S5P_JPEG_ENCODE)
+ q_data->size = exynos4_jpeg_get_output_buffer_size(ct,
+ f,
+ q_data->fmt->depth);
+ else
+ q_data->size = q_data->w * q_data->h *
+ q_data->fmt->depth >> 3;
+ } else {
+ q_data->size = pix->sizeimage;
+ }
+
+ if (f_type == FMT_TYPE_OUTPUT) {
+ ctrl_subs = v4l2_ctrl_find(&ct->ctrl_handler,
+ V4L2_CID_JPEG_CHROMA_SUBSAMPLING);
+ if (ctrl_subs)
+ v4l2_ctrl_s_ctrl(ctrl_subs, q_data->fmt->subsampling);
+ ct->crop_altered = false;
+ }
+
+ /*
+ * For decoding init crop_rect with capture buffer dimmensions which
+ * contain aligned dimensions of the input JPEG image and do it only
+ * if crop rectangle hasn't been altered by the user space e.g. with
+ * S_SELECTION ioctl. For encoding assign output buffer dimensions.
+ */
+ if (!ct->crop_altered &&
+ ((ct->mode == S5P_JPEG_DECODE && f_type == FMT_TYPE_CAPTURE) ||
+ (ct->mode == S5P_JPEG_ENCODE && f_type == FMT_TYPE_OUTPUT))) {
+ ct->crop_rect.width = pix->width;
+ ct->crop_rect.height = pix->height;
+ }
+
+ /*
+ * Prevent downscaling to YUV420 format by more than 2
+ * for Exynos3250/compatible SoC as it produces broken raw image
+ * in such cases.
+ */
+ if (ct->mode == S5P_JPEG_DECODE &&
+ f_type == FMT_TYPE_CAPTURE &&
+ ct->jpeg->variant->hw3250_compat &&
+ pix->pixelformat == V4L2_PIX_FMT_YUV420 &&
+ ct->scale_factor > 2) {
+ scale_rect.width = ct->out_q.w / 2;
+ scale_rect.height = ct->out_q.h / 2;
+ exynos3250_jpeg_try_downscale(ct, &scale_rect);
+ }
+
+ return 0;
+}
+
+static int s5p_jpeg_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ int ret;
+
+ ret = s5p_jpeg_try_fmt_vid_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ return s5p_jpeg_s_fmt(fh_to_ctx(priv), f);
+}
+
+static int s5p_jpeg_s_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ int ret;
+
+ ret = s5p_jpeg_try_fmt_vid_out(file, priv, f);
+ if (ret)
+ return ret;
+
+ return s5p_jpeg_s_fmt(fh_to_ctx(priv), f);
+}
+
+static int s5p_jpeg_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ if (sub->type == V4L2_EVENT_SOURCE_CHANGE)
+ return v4l2_src_change_event_subscribe(fh, sub);
+
+ return -EINVAL;
+}
+
+static int exynos3250_jpeg_try_downscale(struct s5p_jpeg_ctx *ctx,
+ struct v4l2_rect *r)
+{
+ int w_ratio, h_ratio, scale_factor, cur_ratio, i;
+
+ w_ratio = ctx->out_q.w / r->width;
+ h_ratio = ctx->out_q.h / r->height;
+
+ scale_factor = max(w_ratio, h_ratio);
+ scale_factor = clamp_val(scale_factor, 1, 8);
+
+ /* Align scale ratio to the nearest power of 2 */
+ for (i = 0; i <= 3; ++i) {
+ cur_ratio = 1 << i;
+ if (scale_factor <= cur_ratio) {
+ ctx->scale_factor = cur_ratio;
+ break;
+ }
+ }
+
+ r->width = round_down(ctx->out_q.w / ctx->scale_factor, 2);
+ r->height = round_down(ctx->out_q.h / ctx->scale_factor, 2);
+
+ ctx->crop_rect.width = r->width;
+ ctx->crop_rect.height = r->height;
+ ctx->crop_rect.left = 0;
+ ctx->crop_rect.top = 0;
+
+ ctx->crop_altered = true;
+
+ return 0;
+}
+
+static int exynos3250_jpeg_try_crop(struct s5p_jpeg_ctx *ctx,
+ struct v4l2_rect *r)
+{
+ struct v4l2_rect base_rect;
+ int w_step, h_step;
+
+ switch (ctx->cap_q.fmt->fourcc) {
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ w_step = 1;
+ h_step = 2;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ w_step = 2;
+ h_step = 2;
+ break;
+ default:
+ w_step = 1;
+ h_step = 1;
+ break;
+ }
+
+ base_rect.top = 0;
+ base_rect.left = 0;
+ base_rect.width = ctx->out_q.w;
+ base_rect.height = ctx->out_q.h;
+
+ r->width = round_down(r->width, w_step);
+ r->height = round_down(r->height, h_step);
+ r->left = round_down(r->left, 2);
+ r->top = round_down(r->top, 2);
+
+ if (!v4l2_rect_enclosed(r, &base_rect))
+ return -EINVAL;
+
+ ctx->crop_rect.left = r->left;
+ ctx->crop_rect.top = r->top;
+ ctx->crop_rect.width = r->width;
+ ctx->crop_rect.height = r->height;
+
+ ctx->crop_altered = true;
+
+ return 0;
+}
+
+/*
+ * V4L2 controls
+ */
+
+static int s5p_jpeg_g_selection(struct file *file, void *priv,
+ struct v4l2_selection *s)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+ s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ /* For JPEG blob active == default == bounds */
+ switch (s->target) {
+ case V4L2_SEL_TGT_CROP:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ s->r.width = ctx->out_q.w;
+ s->r.height = ctx->out_q.h;
+ s->r.left = 0;
+ s->r.top = 0;
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ case V4L2_SEL_TGT_COMPOSE_PADDED:
+ s->r.width = ctx->crop_rect.width;
+ s->r.height = ctx->crop_rect.height;
+ s->r.left = ctx->crop_rect.left;
+ s->r.top = ctx->crop_rect.top;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * V4L2 controls
+ */
+static int s5p_jpeg_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data);
+ struct v4l2_rect *rect = &s->r;
+ int ret = -EINVAL;
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE) {
+ if (ctx->mode != S5P_JPEG_DECODE)
+ return -EINVAL;
+ if (ctx->jpeg->variant->hw3250_compat)
+ ret = exynos3250_jpeg_try_downscale(ctx, rect);
+ } else if (s->target == V4L2_SEL_TGT_CROP) {
+ if (ctx->mode != S5P_JPEG_ENCODE)
+ return -EINVAL;
+ if (ctx->jpeg->variant->hw3250_compat)
+ ret = exynos3250_jpeg_try_crop(ctx, rect);
+ }
+
+ return ret;
+}
+
+static int s5p_jpeg_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl);
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ unsigned long flags;
+
+ switch (ctrl->id) {
+ case V4L2_CID_JPEG_CHROMA_SUBSAMPLING:
+ spin_lock_irqsave(&jpeg->slock, flags);
+ ctrl->val = s5p_jpeg_to_user_subsampling(ctx);
+ spin_unlock_irqrestore(&jpeg->slock, flags);
+ break;
+ }
+
+ return 0;
+}
+
+static int s5p_jpeg_adjust_subs_ctrl(struct s5p_jpeg_ctx *ctx, int *ctrl_val)
+{
+ switch (ctx->jpeg->variant->version) {
+ case SJPEG_S5P:
+ return 0;
+ case SJPEG_EXYNOS3250:
+ case SJPEG_EXYNOS5420:
+ /*
+ * The exynos3250/compatible device can produce JPEG image only
+ * of 4:4:4 subsampling when given RGB32 source image.
+ */
+ if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB32)
+ *ctrl_val = 0;
+ break;
+ case SJPEG_EXYNOS4:
+ /*
+ * The exynos4x12 device requires input raw image fourcc
+ * to be V4L2_PIX_FMT_GREY if gray jpeg format
+ * is to be set.
+ */
+ if (ctx->out_q.fmt->fourcc != V4L2_PIX_FMT_GREY &&
+ *ctrl_val == V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY)
+ return -EINVAL;
+ break;
+ }
+
+ /*
+ * The exynos4x12 and exynos3250/compatible devices require resulting
+ * jpeg subsampling not to be lower than the input raw image
+ * subsampling.
+ */
+ if (ctx->out_q.fmt->subsampling > *ctrl_val)
+ *ctrl_val = ctx->out_q.fmt->subsampling;
+
+ return 0;
+}
+
+static int s5p_jpeg_try_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&ctx->jpeg->slock, flags);
+
+ if (ctrl->id == V4L2_CID_JPEG_CHROMA_SUBSAMPLING)
+ ret = s5p_jpeg_adjust_subs_ctrl(ctx, &ctrl->val);
+
+ spin_unlock_irqrestore(&ctx->jpeg->slock, flags);
+ return ret;
+}
+
+static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->jpeg->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+ ctx->compr_quality = ctrl->val;
+ break;
+ case V4L2_CID_JPEG_RESTART_INTERVAL:
+ ctx->restart_interval = ctrl->val;
+ break;
+ case V4L2_CID_JPEG_CHROMA_SUBSAMPLING:
+ ctx->subsampling = ctrl->val;
+ break;
+ }
+
+ spin_unlock_irqrestore(&ctx->jpeg->slock, flags);
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops s5p_jpeg_ctrl_ops = {
+ .g_volatile_ctrl = s5p_jpeg_g_volatile_ctrl,
+ .try_ctrl = s5p_jpeg_try_ctrl,
+ .s_ctrl = s5p_jpeg_s_ctrl,
+};
+
+static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx)
+{
+ unsigned int mask = ~0x27; /* 444, 422, 420, GRAY */
+ struct v4l2_ctrl *ctrl;
+ int ret;
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3);
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops,
+ V4L2_CID_JPEG_COMPRESSION_QUALITY,
+ 0, 3, 1, S5P_JPEG_COMPR_QUAL_WORST);
+
+ v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops,
+ V4L2_CID_JPEG_RESTART_INTERVAL,
+ 0, 0xffff, 1, 0);
+ if (ctx->jpeg->variant->version == SJPEG_S5P)
+ mask = ~0x06; /* 422, 420 */
+ }
+
+ ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops,
+ V4L2_CID_JPEG_CHROMA_SUBSAMPLING,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, mask,
+ V4L2_JPEG_CHROMA_SUBSAMPLING_422);
+
+ if (ctx->ctrl_handler.error) {
+ ret = ctx->ctrl_handler.error;
+ goto error_free;
+ }
+
+ if (ctx->mode == S5P_JPEG_DECODE)
+ ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE |
+ V4L2_CTRL_FLAG_READ_ONLY;
+
+ ret = v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+ if (ret < 0)
+ goto error_free;
+
+ return ret;
+
+error_free:
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops s5p_jpeg_ioctl_ops = {
+ .vidioc_querycap = s5p_jpeg_querycap,
+
+ .vidioc_enum_fmt_vid_cap = s5p_jpeg_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = s5p_jpeg_enum_fmt_vid_out,
+
+ .vidioc_g_fmt_vid_cap = s5p_jpeg_g_fmt,
+ .vidioc_g_fmt_vid_out = s5p_jpeg_g_fmt,
+
+ .vidioc_try_fmt_vid_cap = s5p_jpeg_try_fmt_vid_cap,
+ .vidioc_try_fmt_vid_out = s5p_jpeg_try_fmt_vid_out,
+
+ .vidioc_s_fmt_vid_cap = s5p_jpeg_s_fmt_vid_cap,
+ .vidioc_s_fmt_vid_out = s5p_jpeg_s_fmt_vid_out,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+ .vidioc_g_selection = s5p_jpeg_g_selection,
+ .vidioc_s_selection = s5p_jpeg_s_selection,
+
+ .vidioc_subscribe_event = s5p_jpeg_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/*
+ * ============================================================================
+ * mem2mem callbacks
+ * ============================================================================
+ */
+
+static void s5p_jpeg_device_run(void *priv)
+{
+ struct s5p_jpeg_ctx *ctx = priv;
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ unsigned long src_addr, dst_addr, flags;
+
+ spin_lock_irqsave(&ctx->jpeg->slock, flags);
+
+ src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ src_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
+
+ s5p_jpeg_reset(jpeg->regs);
+ s5p_jpeg_poweron(jpeg->regs);
+ s5p_jpeg_proc_mode(jpeg->regs, ctx->mode);
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565)
+ s5p_jpeg_input_raw_mode(jpeg->regs,
+ S5P_JPEG_RAW_IN_565);
+ else
+ s5p_jpeg_input_raw_mode(jpeg->regs,
+ S5P_JPEG_RAW_IN_422);
+ s5p_jpeg_subsampling_mode(jpeg->regs, ctx->subsampling);
+ s5p_jpeg_dri(jpeg->regs, ctx->restart_interval);
+ s5p_jpeg_x(jpeg->regs, ctx->out_q.w);
+ s5p_jpeg_y(jpeg->regs, ctx->out_q.h);
+ s5p_jpeg_imgadr(jpeg->regs, src_addr);
+ s5p_jpeg_jpgadr(jpeg->regs, dst_addr);
+
+ /* ultimately comes from sizeimage from userspace */
+ s5p_jpeg_enc_stream_int(jpeg->regs, ctx->cap_q.size);
+
+ /* JPEG RGB to YCbCr conversion matrix */
+ s5p_jpeg_coef(jpeg->regs, 1, 1, S5P_JPEG_COEF11);
+ s5p_jpeg_coef(jpeg->regs, 1, 2, S5P_JPEG_COEF12);
+ s5p_jpeg_coef(jpeg->regs, 1, 3, S5P_JPEG_COEF13);
+ s5p_jpeg_coef(jpeg->regs, 2, 1, S5P_JPEG_COEF21);
+ s5p_jpeg_coef(jpeg->regs, 2, 2, S5P_JPEG_COEF22);
+ s5p_jpeg_coef(jpeg->regs, 2, 3, S5P_JPEG_COEF23);
+ s5p_jpeg_coef(jpeg->regs, 3, 1, S5P_JPEG_COEF31);
+ s5p_jpeg_coef(jpeg->regs, 3, 2, S5P_JPEG_COEF32);
+ s5p_jpeg_coef(jpeg->regs, 3, 3, S5P_JPEG_COEF33);
+
+ /*
+ * JPEG IP allows storing 4 quantization tables
+ * We fill table 0 for luma and table 1 for chroma
+ */
+ s5p_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality);
+ s5p_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality);
+ /* use table 0 for Y */
+ s5p_jpeg_qtbl(jpeg->regs, 1, 0);
+ /* use table 1 for Cb and Cr*/
+ s5p_jpeg_qtbl(jpeg->regs, 2, 1);
+ s5p_jpeg_qtbl(jpeg->regs, 3, 1);
+
+ /* Y, Cb, Cr use Huffman table 0 */
+ s5p_jpeg_htbl_ac(jpeg->regs, 1);
+ s5p_jpeg_htbl_dc(jpeg->regs, 1);
+ s5p_jpeg_htbl_ac(jpeg->regs, 2);
+ s5p_jpeg_htbl_dc(jpeg->regs, 2);
+ s5p_jpeg_htbl_ac(jpeg->regs, 3);
+ s5p_jpeg_htbl_dc(jpeg->regs, 3);
+ } else { /* S5P_JPEG_DECODE */
+ s5p_jpeg_rst_int_enable(jpeg->regs, true);
+ s5p_jpeg_data_num_int_enable(jpeg->regs, true);
+ s5p_jpeg_final_mcu_num_int_enable(jpeg->regs, true);
+ if (ctx->cap_q.fmt->fourcc == V4L2_PIX_FMT_YUYV)
+ s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422);
+ else
+ s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_420);
+ s5p_jpeg_jpgadr(jpeg->regs, src_addr);
+ s5p_jpeg_imgadr(jpeg->regs, dst_addr);
+ }
+
+ s5p_jpeg_start(jpeg->regs);
+
+ spin_unlock_irqrestore(&ctx->jpeg->slock, flags);
+}
+
+static void exynos4_jpeg_set_img_addr(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct s5p_jpeg_fmt *fmt;
+ struct vb2_v4l2_buffer *vb;
+ struct s5p_jpeg_addr jpeg_addr = {};
+ u32 pix_size, padding_bytes = 0;
+
+ jpeg_addr.cb = 0;
+ jpeg_addr.cr = 0;
+
+ pix_size = ctx->cap_q.w * ctx->cap_q.h;
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ fmt = ctx->out_q.fmt;
+ if (ctx->out_q.w % 2 && fmt->h_align > 0)
+ padding_bytes = ctx->out_q.h;
+ } else {
+ fmt = ctx->cap_q.fmt;
+ vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ }
+
+ jpeg_addr.y = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0);
+
+ if (fmt->colplanes == 2) {
+ jpeg_addr.cb = jpeg_addr.y + pix_size - padding_bytes;
+ } else if (fmt->colplanes == 3) {
+ jpeg_addr.cb = jpeg_addr.y + pix_size;
+ if (fmt->fourcc == V4L2_PIX_FMT_YUV420)
+ jpeg_addr.cr = jpeg_addr.cb + pix_size / 4;
+ else
+ jpeg_addr.cr = jpeg_addr.cb + pix_size / 2;
+ }
+
+ exynos4_jpeg_set_frame_buf_address(jpeg->regs, &jpeg_addr);
+}
+
+static void exynos4_jpeg_set_jpeg_addr(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb;
+ unsigned int jpeg_addr = 0;
+
+ if (ctx->mode == S5P_JPEG_ENCODE)
+ vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ else
+ vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+
+ jpeg_addr = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0);
+ if (jpeg->variant->version == SJPEG_EXYNOS5433 &&
+ ctx->mode == S5P_JPEG_DECODE)
+ jpeg_addr += ctx->out_q.sos;
+ exynos4_jpeg_set_stream_buf_address(jpeg->regs, jpeg_addr);
+}
+
+static inline void exynos4_jpeg_set_img_fmt(void __iomem *base,
+ unsigned int img_fmt)
+{
+ __exynos4_jpeg_set_img_fmt(base, img_fmt, SJPEG_EXYNOS4);
+}
+
+static inline void exynos5433_jpeg_set_img_fmt(void __iomem *base,
+ unsigned int img_fmt)
+{
+ __exynos4_jpeg_set_img_fmt(base, img_fmt, SJPEG_EXYNOS5433);
+}
+
+static inline void exynos4_jpeg_set_enc_out_fmt(void __iomem *base,
+ unsigned int out_fmt)
+{
+ __exynos4_jpeg_set_enc_out_fmt(base, out_fmt, SJPEG_EXYNOS4);
+}
+
+static inline void exynos5433_jpeg_set_enc_out_fmt(void __iomem *base,
+ unsigned int out_fmt)
+{
+ __exynos4_jpeg_set_enc_out_fmt(base, out_fmt, SJPEG_EXYNOS5433);
+}
+
+static void exynos4_jpeg_device_run(void *priv)
+{
+ struct s5p_jpeg_ctx *ctx = priv;
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ unsigned int bitstream_size;
+ unsigned long flags;
+
+ spin_lock_irqsave(&jpeg->slock, flags);
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ exynos4_jpeg_sw_reset(jpeg->regs);
+ exynos4_jpeg_set_interrupt(jpeg->regs, jpeg->variant->version);
+ exynos4_jpeg_set_huf_table_enable(jpeg->regs, 1);
+
+ exynos4_jpeg_set_huff_tbl(jpeg->regs);
+
+ /*
+ * JPEG IP allows storing 4 quantization tables
+ * We fill table 0 for luma and table 1 for chroma
+ */
+ exynos4_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality);
+ exynos4_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality);
+
+ exynos4_jpeg_set_encode_tbl_select(jpeg->regs,
+ ctx->compr_quality);
+ exynos4_jpeg_set_stream_size(jpeg->regs, ctx->cap_q.w,
+ ctx->cap_q.h);
+
+ if (ctx->jpeg->variant->version == SJPEG_EXYNOS4) {
+ exynos4_jpeg_set_enc_out_fmt(jpeg->regs,
+ ctx->subsampling);
+ exynos4_jpeg_set_img_fmt(jpeg->regs,
+ ctx->out_q.fmt->fourcc);
+ } else {
+ exynos5433_jpeg_set_enc_out_fmt(jpeg->regs,
+ ctx->subsampling);
+ exynos5433_jpeg_set_img_fmt(jpeg->regs,
+ ctx->out_q.fmt->fourcc);
+ }
+ exynos4_jpeg_set_img_addr(ctx);
+ exynos4_jpeg_set_jpeg_addr(ctx);
+ exynos4_jpeg_set_encode_hoff_cnt(jpeg->regs,
+ ctx->out_q.fmt->fourcc);
+ } else {
+ exynos4_jpeg_sw_reset(jpeg->regs);
+ exynos4_jpeg_set_interrupt(jpeg->regs,
+ jpeg->variant->version);
+ exynos4_jpeg_set_img_addr(ctx);
+ exynos4_jpeg_set_jpeg_addr(ctx);
+
+ if (jpeg->variant->version == SJPEG_EXYNOS5433) {
+ exynos4_jpeg_parse_huff_tbl(ctx);
+ exynos4_jpeg_parse_decode_h_tbl(ctx);
+
+ exynos4_jpeg_parse_q_tbl(ctx);
+ exynos4_jpeg_parse_decode_q_tbl(ctx);
+
+ exynos4_jpeg_set_huf_table_enable(jpeg->regs, 1);
+
+ exynos4_jpeg_set_stream_size(jpeg->regs, ctx->cap_q.w,
+ ctx->cap_q.h);
+ exynos5433_jpeg_set_enc_out_fmt(jpeg->regs,
+ ctx->subsampling);
+ exynos5433_jpeg_set_img_fmt(jpeg->regs,
+ ctx->cap_q.fmt->fourcc);
+ bitstream_size = DIV_ROUND_UP(ctx->out_q.size, 16);
+ } else {
+ exynos4_jpeg_set_img_fmt(jpeg->regs,
+ ctx->cap_q.fmt->fourcc);
+ bitstream_size = DIV_ROUND_UP(ctx->out_q.size, 32);
+ }
+
+ exynos4_jpeg_set_dec_bitstream_size(jpeg->regs, bitstream_size);
+ }
+
+ exynos4_jpeg_set_sys_int_enable(jpeg->regs, 1);
+ exynos4_jpeg_set_enc_dec_mode(jpeg->regs, ctx->mode);
+
+ spin_unlock_irqrestore(&jpeg->slock, flags);
+}
+
+static void exynos3250_jpeg_set_img_addr(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct s5p_jpeg_fmt *fmt;
+ struct vb2_v4l2_buffer *vb;
+ struct s5p_jpeg_addr jpeg_addr = {};
+ u32 pix_size;
+
+ pix_size = ctx->cap_q.w * ctx->cap_q.h;
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+ fmt = ctx->out_q.fmt;
+ } else {
+ vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ fmt = ctx->cap_q.fmt;
+ }
+
+ jpeg_addr.y = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0);
+
+ if (fmt->colplanes == 2) {
+ jpeg_addr.cb = jpeg_addr.y + pix_size;
+ } else if (fmt->colplanes == 3) {
+ jpeg_addr.cb = jpeg_addr.y + pix_size;
+ if (fmt->fourcc == V4L2_PIX_FMT_YUV420)
+ jpeg_addr.cr = jpeg_addr.cb + pix_size / 4;
+ else
+ jpeg_addr.cr = jpeg_addr.cb + pix_size / 2;
+ }
+
+ exynos3250_jpeg_imgadr(jpeg->regs, &jpeg_addr);
+}
+
+static void exynos3250_jpeg_set_jpeg_addr(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ struct vb2_v4l2_buffer *vb;
+ unsigned int jpeg_addr = 0;
+
+ if (ctx->mode == S5P_JPEG_ENCODE)
+ vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+ else
+ vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+
+ jpeg_addr = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0);
+ exynos3250_jpeg_jpgadr(jpeg->regs, jpeg_addr);
+}
+
+static void exynos3250_jpeg_device_run(void *priv)
+{
+ struct s5p_jpeg_ctx *ctx = priv;
+ struct s5p_jpeg *jpeg = ctx->jpeg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->jpeg->slock, flags);
+
+ exynos3250_jpeg_reset(jpeg->regs);
+ exynos3250_jpeg_set_dma_num(jpeg->regs);
+ exynos3250_jpeg_poweron(jpeg->regs);
+ exynos3250_jpeg_clk_set(jpeg->regs);
+ exynos3250_jpeg_proc_mode(jpeg->regs, ctx->mode);
+
+ if (ctx->mode == S5P_JPEG_ENCODE) {
+ exynos3250_jpeg_input_raw_fmt(jpeg->regs,
+ ctx->out_q.fmt->fourcc);
+ exynos3250_jpeg_dri(jpeg->regs, ctx->restart_interval);
+
+ /*
+ * JPEG IP allows storing 4 quantization tables
+ * We fill table 0 for luma and table 1 for chroma
+ */
+ s5p_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality);
+ s5p_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality);
+ /* use table 0 for Y */
+ exynos3250_jpeg_qtbl(jpeg->regs, 1, 0);
+ /* use table 1 for Cb and Cr*/
+ exynos3250_jpeg_qtbl(jpeg->regs, 2, 1);
+ exynos3250_jpeg_qtbl(jpeg->regs, 3, 1);
+
+ /*
+ * Some SoCs require setting Huffman tables before each run
+ */
+ if (jpeg->variant->htbl_reinit) {
+ s5p_jpeg_set_hdctbl(jpeg->regs);
+ s5p_jpeg_set_hdctblg(jpeg->regs);
+ s5p_jpeg_set_hactbl(jpeg->regs);
+ s5p_jpeg_set_hactblg(jpeg->regs);
+ }
+
+ /* Y, Cb, Cr use Huffman table 0 */
+ exynos3250_jpeg_htbl_ac(jpeg->regs, 1);
+ exynos3250_jpeg_htbl_dc(jpeg->regs, 1);
+ exynos3250_jpeg_htbl_ac(jpeg->regs, 2);
+ exynos3250_jpeg_htbl_dc(jpeg->regs, 2);
+ exynos3250_jpeg_htbl_ac(jpeg->regs, 3);
+ exynos3250_jpeg_htbl_dc(jpeg->regs, 3);
+
+ exynos3250_jpeg_set_x(jpeg->regs, ctx->crop_rect.width);
+ exynos3250_jpeg_set_y(jpeg->regs, ctx->crop_rect.height);
+ exynos3250_jpeg_stride(jpeg->regs, ctx->out_q.fmt->fourcc,
+ ctx->out_q.w);
+ exynos3250_jpeg_offset(jpeg->regs, ctx->crop_rect.left,
+ ctx->crop_rect.top);
+ exynos3250_jpeg_set_img_addr(ctx);
+ exynos3250_jpeg_set_jpeg_addr(ctx);
+ exynos3250_jpeg_subsampling_mode(jpeg->regs, ctx->subsampling);
+
+ /* ultimately comes from sizeimage from userspace */
+ exynos3250_jpeg_enc_stream_bound(jpeg->regs, ctx->cap_q.size);
+
+ if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565 ||
+ ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565X ||
+ ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB32)
+ exynos3250_jpeg_set_y16(jpeg->regs, true);
+ } else {
+ exynos3250_jpeg_set_img_addr(ctx);
+ exynos3250_jpeg_set_jpeg_addr(ctx);
+ exynos3250_jpeg_stride(jpeg->regs, ctx->cap_q.fmt->fourcc,
+ ctx->cap_q.w);
+ exynos3250_jpeg_offset(jpeg->regs, 0, 0);
+ exynos3250_jpeg_dec_scaling_ratio(jpeg->regs,
+ ctx->scale_factor);
+ exynos3250_jpeg_dec_stream_size(jpeg->regs, ctx->out_q.size);
+ exynos3250_jpeg_output_raw_fmt(jpeg->regs,
+ ctx->cap_q.fmt->fourcc);
+ }
+
+ exynos3250_jpeg_interrupts_enable(jpeg->regs);
+
+ /* JPEG RGB to YCbCr conversion matrix */
+ exynos3250_jpeg_coef(jpeg->regs, ctx->mode);
+
+ exynos3250_jpeg_set_timer(jpeg->regs, EXYNOS3250_IRQ_TIMEOUT);
+ jpeg->irq_status = 0;
+ exynos3250_jpeg_start(jpeg->regs);
+
+ spin_unlock_irqrestore(&ctx->jpeg->slock, flags);
+}
+
+static int s5p_jpeg_job_ready(void *priv)
+{
+ struct s5p_jpeg_ctx *ctx = priv;
+
+ if (ctx->mode == S5P_JPEG_DECODE) {
+ /*
+ * We have only one input buffer and one output buffer. If there
+ * is a resolution change event, no need to continue decoding.
+ */
+ if (ctx->state == JPEGCTX_RESOLUTION_CHANGE)
+ return 0;
+
+ return ctx->hdr_parsed;
+ }
+
+ return 1;
+}
+
+static const struct v4l2_m2m_ops s5p_jpeg_m2m_ops = {
+ .device_run = s5p_jpeg_device_run,
+ .job_ready = s5p_jpeg_job_ready,
+};
+
+static const struct v4l2_m2m_ops exynos3250_jpeg_m2m_ops = {
+ .device_run = exynos3250_jpeg_device_run,
+ .job_ready = s5p_jpeg_job_ready,
+};
+
+static const struct v4l2_m2m_ops exynos4_jpeg_m2m_ops = {
+ .device_run = exynos4_jpeg_device_run,
+ .job_ready = s5p_jpeg_job_ready,
+};
+
+/*
+ * ============================================================================
+ * Queue operations
+ * ============================================================================
+ */
+
+static int s5p_jpeg_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers, unsigned int *nplanes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vq);
+ struct s5p_jpeg_q_data *q_data = NULL;
+ unsigned int size, count = *nbuffers;
+
+ q_data = get_q_data(ctx, vq->type);
+ BUG_ON(q_data == NULL);
+
+ size = q_data->size;
+
+ /*
+ * header is parsed during decoding and parsed information stored
+ * in the context so we do not allow another buffer to overwrite it
+ */
+ if (ctx->mode == S5P_JPEG_DECODE)
+ count = 1;
+
+ *nbuffers = count;
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int s5p_jpeg_buf_prepare(struct vb2_buffer *vb)
+{
+ struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct s5p_jpeg_q_data *q_data = NULL;
+
+ q_data = get_q_data(ctx, vb->vb2_queue->type);
+ BUG_ON(q_data == NULL);
+
+ if (vb2_plane_size(vb, 0) < q_data->size) {
+ pr_err("%s data will not fit into plane (%lu < %lu)\n",
+ __func__, vb2_plane_size(vb, 0),
+ (long)q_data->size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, q_data->size);
+
+ return 0;
+}
+
+static void s5p_jpeg_set_capture_queue_data(struct s5p_jpeg_ctx *ctx)
+{
+ struct s5p_jpeg_q_data *q_data = &ctx->cap_q;
+
+ q_data->w = ctx->out_q.w;
+ q_data->h = ctx->out_q.h;
+
+ /*
+ * This call to jpeg_bound_align_image() takes care of width and
+ * height values alignment when user space calls the QBUF of
+ * OUTPUT buffer after the S_FMT of CAPTURE buffer.
+ * Please note that on Exynos4x12 SoCs, resigning from executing
+ * S_FMT on capture buffer for each JPEG image can result in a
+ * hardware hangup if subsampling is lower than the one of input
+ * JPEG.
+ */
+ jpeg_bound_align_image(ctx, &q_data->w, S5P_JPEG_MIN_WIDTH,
+ S5P_JPEG_MAX_WIDTH, q_data->fmt->h_align,
+ &q_data->h, S5P_JPEG_MIN_HEIGHT,
+ S5P_JPEG_MAX_HEIGHT, q_data->fmt->v_align);
+
+ q_data->size = q_data->w * q_data->h * q_data->fmt->depth >> 3;
+}
+
+static void s5p_jpeg_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+ if (ctx->mode == S5P_JPEG_DECODE &&
+ vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ static const struct v4l2_event ev_src_ch = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+ };
+ struct vb2_queue *dst_vq;
+ u32 ori_w;
+ u32 ori_h;
+
+ dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+ V4L2_BUF_TYPE_VIDEO_CAPTURE);
+ ori_w = ctx->out_q.w;
+ ori_h = ctx->out_q.h;
+
+ ctx->hdr_parsed = s5p_jpeg_parse_hdr(&ctx->out_q,
+ (unsigned long)vb2_plane_vaddr(vb, 0),
+ min((unsigned long)ctx->out_q.size,
+ vb2_get_plane_payload(vb, 0)), ctx);
+ if (!ctx->hdr_parsed) {
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ return;
+ }
+
+ /*
+ * If there is a resolution change event, only update capture
+ * queue when it is not streaming. Otherwise, update it in
+ * STREAMOFF. See s5p_jpeg_stop_streaming for detail.
+ */
+ if (ctx->out_q.w != ori_w || ctx->out_q.h != ori_h) {
+ v4l2_event_queue_fh(&ctx->fh, &ev_src_ch);
+ if (vb2_is_streaming(dst_vq))
+ ctx->state = JPEGCTX_RESOLUTION_CHANGE;
+ else
+ s5p_jpeg_set_capture_queue_data(ctx);
+ }
+ }
+
+ v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static int s5p_jpeg_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(q);
+
+ return pm_runtime_resume_and_get(ctx->jpeg->dev);
+}
+
+static void s5p_jpeg_stop_streaming(struct vb2_queue *q)
+{
+ struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(q);
+
+ /*
+ * STREAMOFF is an acknowledgment for resolution change event.
+ * Before STREAMOFF, we still have to return the old resolution and
+ * subsampling. Update capture queue when the stream is off.
+ */
+ if (ctx->state == JPEGCTX_RESOLUTION_CHANGE &&
+ q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+ s5p_jpeg_set_capture_queue_data(ctx);
+ ctx->state = JPEGCTX_RUNNING;
+ }
+
+ pm_runtime_put(ctx->jpeg->dev);
+}
+
+static const struct vb2_ops s5p_jpeg_qops = {
+ .queue_setup = s5p_jpeg_queue_setup,
+ .buf_prepare = s5p_jpeg_buf_prepare,
+ .buf_queue = s5p_jpeg_buf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = s5p_jpeg_start_streaming,
+ .stop_streaming = s5p_jpeg_stop_streaming,
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct s5p_jpeg_ctx *ctx = priv;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR;
+ src_vq->drv_priv = ctx;
+ src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ src_vq->ops = &s5p_jpeg_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &ctx->jpeg->lock;
+ src_vq->dev = ctx->jpeg->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR;
+ dst_vq->drv_priv = ctx;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->ops = &s5p_jpeg_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &ctx->jpeg->lock;
+ dst_vq->dev = ctx->jpeg->dev;
+
+ return vb2_queue_init(dst_vq);
+}
+
+/*
+ * ============================================================================
+ * ISR
+ * ============================================================================
+ */
+
+static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id)
+{
+ struct s5p_jpeg *jpeg = dev_id;
+ struct s5p_jpeg_ctx *curr_ctx;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ unsigned long payload_size = 0;
+ enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
+ bool enc_jpeg_too_large = false;
+ bool timer_elapsed = false;
+ bool op_completed = false;
+
+ spin_lock(&jpeg->slock);
+
+ curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev);
+
+ src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx);
+
+ if (curr_ctx->mode == S5P_JPEG_ENCODE)
+ enc_jpeg_too_large = s5p_jpeg_enc_stream_stat(jpeg->regs);
+ timer_elapsed = s5p_jpeg_timer_stat(jpeg->regs);
+ op_completed = s5p_jpeg_result_stat_ok(jpeg->regs);
+ if (curr_ctx->mode == S5P_JPEG_DECODE)
+ op_completed = op_completed &&
+ s5p_jpeg_stream_stat_ok(jpeg->regs);
+
+ if (enc_jpeg_too_large) {
+ state = VB2_BUF_STATE_ERROR;
+ s5p_jpeg_clear_enc_stream_stat(jpeg->regs);
+ } else if (timer_elapsed) {
+ state = VB2_BUF_STATE_ERROR;
+ s5p_jpeg_clear_timer_stat(jpeg->regs);
+ } else if (!op_completed) {
+ state = VB2_BUF_STATE_ERROR;
+ } else {
+ payload_size = s5p_jpeg_compressed_size(jpeg->regs);
+ }
+
+ dst_buf->timecode = src_buf->timecode;
+ dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp;
+ dst_buf->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ dst_buf->flags |=
+ src_buf->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+
+ v4l2_m2m_buf_done(src_buf, state);
+ if (curr_ctx->mode == S5P_JPEG_ENCODE)
+ vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size);
+ v4l2_m2m_buf_done(dst_buf, state);
+
+ curr_ctx->subsampling = s5p_jpeg_get_subsampling_mode(jpeg->regs);
+ spin_unlock(&jpeg->slock);
+
+ s5p_jpeg_clear_int(jpeg->regs);
+
+ v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos4_jpeg_irq(int irq, void *priv)
+{
+ unsigned int int_status;
+ struct vb2_v4l2_buffer *src_vb, *dst_vb;
+ struct s5p_jpeg *jpeg = priv;
+ struct s5p_jpeg_ctx *curr_ctx;
+ unsigned long payload_size = 0;
+
+ spin_lock(&jpeg->slock);
+
+ exynos4_jpeg_set_sys_int_enable(jpeg->regs, 0);
+
+ curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev);
+
+ src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx);
+ dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx);
+
+ int_status = exynos4_jpeg_get_int_status(jpeg->regs);
+
+ if (int_status) {
+ switch (int_status & 0x1f) {
+ case 0x1:
+ jpeg->irq_ret = ERR_PROT;
+ break;
+ case 0x2:
+ jpeg->irq_ret = OK_ENC_OR_DEC;
+ break;
+ case 0x4:
+ jpeg->irq_ret = ERR_DEC_INVALID_FORMAT;
+ break;
+ case 0x8:
+ jpeg->irq_ret = ERR_MULTI_SCAN;
+ break;
+ case 0x10:
+ jpeg->irq_ret = ERR_FRAME;
+ break;
+ default:
+ jpeg->irq_ret = ERR_UNKNOWN;
+ break;
+ }
+ } else {
+ jpeg->irq_ret = ERR_UNKNOWN;
+ }
+
+ if (jpeg->irq_ret == OK_ENC_OR_DEC) {
+ if (curr_ctx->mode == S5P_JPEG_ENCODE) {
+ payload_size = exynos4_jpeg_get_stream_size(jpeg->regs);
+ vb2_set_plane_payload(&dst_vb->vb2_buf,
+ 0, payload_size);
+ }
+ v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE);
+ v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE);
+ } else {
+ v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_ERROR);
+ v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR);
+ }
+
+ if (jpeg->variant->version == SJPEG_EXYNOS4)
+ curr_ctx->subsampling = exynos4_jpeg_get_frame_fmt(jpeg->regs);
+
+ exynos4_jpeg_set_enc_dec_mode(jpeg->regs, S5P_JPEG_DISABLE);
+
+ spin_unlock(&jpeg->slock);
+
+ v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos3250_jpeg_irq(int irq, void *dev_id)
+{
+ struct s5p_jpeg *jpeg = dev_id;
+ struct s5p_jpeg_ctx *curr_ctx;
+ struct vb2_v4l2_buffer *src_buf, *dst_buf;
+ unsigned long payload_size = 0;
+ enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
+ bool interrupt_timeout = false;
+ bool stream_error = false;
+ u32 irq_status;
+
+ spin_lock(&jpeg->slock);
+
+ irq_status = exynos3250_jpeg_get_timer_status(jpeg->regs);
+ if (irq_status & EXYNOS3250_TIMER_INT_STAT) {
+ exynos3250_jpeg_clear_timer_status(jpeg->regs);
+ interrupt_timeout = true;
+ dev_err(jpeg->dev, "Interrupt timeout occurred.\n");
+ }
+
+ irq_status = exynos3250_jpeg_get_int_status(jpeg->regs);
+ exynos3250_jpeg_clear_int_status(jpeg->regs, irq_status);
+
+ jpeg->irq_status |= irq_status;
+
+ if (jpeg->variant->version == SJPEG_EXYNOS5420 &&
+ irq_status & EXYNOS3250_STREAM_STAT) {
+ stream_error = true;
+ dev_err(jpeg->dev, "Syntax error or unrecoverable error occurred.\n");
+ }
+
+ curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev);
+
+ if (!curr_ctx)
+ goto exit_unlock;
+
+ if ((irq_status & EXYNOS3250_HEADER_STAT) &&
+ (curr_ctx->mode == S5P_JPEG_DECODE)) {
+ exynos3250_jpeg_rstart(jpeg->regs);
+ goto exit_unlock;
+ }
+
+ if (jpeg->irq_status & (EXYNOS3250_JPEG_DONE |
+ EXYNOS3250_WDMA_DONE |
+ EXYNOS3250_RDMA_DONE |
+ EXYNOS3250_RESULT_STAT))
+ payload_size = exynos3250_jpeg_compressed_size(jpeg->regs);
+ else if (interrupt_timeout || stream_error)
+ state = VB2_BUF_STATE_ERROR;
+ else
+ goto exit_unlock;
+
+ src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx);
+ dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx);
+
+ dst_buf->timecode = src_buf->timecode;
+ dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp;
+
+ v4l2_m2m_buf_done(src_buf, state);
+ if (curr_ctx->mode == S5P_JPEG_ENCODE)
+ vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size);
+ v4l2_m2m_buf_done(dst_buf, state);
+
+ curr_ctx->subsampling =
+ exynos3250_jpeg_get_subsampling_mode(jpeg->regs);
+
+ spin_unlock(&jpeg->slock);
+
+ v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx);
+ return IRQ_HANDLED;
+
+exit_unlock:
+ spin_unlock(&jpeg->slock);
+ return IRQ_HANDLED;
+}
+
+static void *jpeg_get_drv_data(struct device *dev);
+
+/*
+ * ============================================================================
+ * Driver basic infrastructure
+ * ============================================================================
+ */
+
+static int s5p_jpeg_probe(struct platform_device *pdev)
+{
+ struct s5p_jpeg *jpeg;
+ int i, ret;
+
+ /* JPEG IP abstraction struct */
+ jpeg = devm_kzalloc(&pdev->dev, sizeof(struct s5p_jpeg), GFP_KERNEL);
+ if (!jpeg)
+ return -ENOMEM;
+
+ jpeg->variant = jpeg_get_drv_data(&pdev->dev);
+ if (!jpeg->variant)
+ return -ENODEV;
+
+ mutex_init(&jpeg->lock);
+ spin_lock_init(&jpeg->slock);
+ jpeg->dev = &pdev->dev;
+
+ /* memory-mapped registers */
+ jpeg->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(jpeg->regs))
+ return PTR_ERR(jpeg->regs);
+
+ /* interrupt service routine registration */
+ jpeg->irq = ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_request_irq(&pdev->dev, jpeg->irq, jpeg->variant->jpeg_irq,
+ 0, dev_name(&pdev->dev), jpeg);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot claim IRQ %d\n", jpeg->irq);
+ return ret;
+ }
+
+ /* clocks */
+ for (i = 0; i < jpeg->variant->num_clocks; i++) {
+ jpeg->clocks[i] = devm_clk_get(&pdev->dev,
+ jpeg->variant->clk_names[i]);
+ if (IS_ERR(jpeg->clocks[i])) {
+ dev_err(&pdev->dev, "failed to get clock: %s\n",
+ jpeg->variant->clk_names[i]);
+ return PTR_ERR(jpeg->clocks[i]);
+ }
+ }
+
+ /* v4l2 device */
+ ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register v4l2 device\n");
+ return ret;
+ }
+
+ /* mem2mem device */
+ jpeg->m2m_dev = v4l2_m2m_init(jpeg->variant->m2m_ops);
+ if (IS_ERR(jpeg->m2m_dev)) {
+ v4l2_err(&jpeg->v4l2_dev, "Failed to init mem2mem device\n");
+ ret = PTR_ERR(jpeg->m2m_dev);
+ goto device_register_rollback;
+ }
+
+ vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32));
+
+ /* JPEG encoder /dev/videoX node */
+ jpeg->vfd_encoder = video_device_alloc();
+ if (!jpeg->vfd_encoder) {
+ v4l2_err(&jpeg->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto m2m_init_rollback;
+ }
+ snprintf(jpeg->vfd_encoder->name, sizeof(jpeg->vfd_encoder->name),
+ "%s-enc", S5P_JPEG_M2M_NAME);
+ jpeg->vfd_encoder->fops = &s5p_jpeg_fops;
+ jpeg->vfd_encoder->ioctl_ops = &s5p_jpeg_ioctl_ops;
+ jpeg->vfd_encoder->minor = -1;
+ jpeg->vfd_encoder->release = video_device_release;
+ jpeg->vfd_encoder->lock = &jpeg->lock;
+ jpeg->vfd_encoder->v4l2_dev = &jpeg->v4l2_dev;
+ jpeg->vfd_encoder->vfl_dir = VFL_DIR_M2M;
+ jpeg->vfd_encoder->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M;
+
+ ret = video_register_device(jpeg->vfd_encoder, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n");
+ video_device_release(jpeg->vfd_encoder);
+ goto m2m_init_rollback;
+ }
+
+ video_set_drvdata(jpeg->vfd_encoder, jpeg);
+ v4l2_info(&jpeg->v4l2_dev,
+ "encoder device registered as /dev/video%d\n",
+ jpeg->vfd_encoder->num);
+
+ /* JPEG decoder /dev/videoX node */
+ jpeg->vfd_decoder = video_device_alloc();
+ if (!jpeg->vfd_decoder) {
+ v4l2_err(&jpeg->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto enc_vdev_register_rollback;
+ }
+ snprintf(jpeg->vfd_decoder->name, sizeof(jpeg->vfd_decoder->name),
+ "%s-dec", S5P_JPEG_M2M_NAME);
+ jpeg->vfd_decoder->fops = &s5p_jpeg_fops;
+ jpeg->vfd_decoder->ioctl_ops = &s5p_jpeg_ioctl_ops;
+ jpeg->vfd_decoder->minor = -1;
+ jpeg->vfd_decoder->release = video_device_release;
+ jpeg->vfd_decoder->lock = &jpeg->lock;
+ jpeg->vfd_decoder->v4l2_dev = &jpeg->v4l2_dev;
+ jpeg->vfd_decoder->vfl_dir = VFL_DIR_M2M;
+ jpeg->vfd_decoder->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M;
+
+ ret = video_register_device(jpeg->vfd_decoder, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n");
+ video_device_release(jpeg->vfd_decoder);
+ goto enc_vdev_register_rollback;
+ }
+
+ video_set_drvdata(jpeg->vfd_decoder, jpeg);
+ v4l2_info(&jpeg->v4l2_dev,
+ "decoder device registered as /dev/video%d\n",
+ jpeg->vfd_decoder->num);
+
+ /* final statements & power management */
+ platform_set_drvdata(pdev, jpeg);
+
+ pm_runtime_enable(&pdev->dev);
+
+ v4l2_info(&jpeg->v4l2_dev, "Samsung S5P JPEG codec\n");
+
+ return 0;
+
+enc_vdev_register_rollback:
+ video_unregister_device(jpeg->vfd_encoder);
+
+m2m_init_rollback:
+ v4l2_m2m_release(jpeg->m2m_dev);
+
+device_register_rollback:
+ v4l2_device_unregister(&jpeg->v4l2_dev);
+
+ return ret;
+}
+
+static void s5p_jpeg_remove(struct platform_device *pdev)
+{
+ struct s5p_jpeg *jpeg = platform_get_drvdata(pdev);
+ int i;
+
+ pm_runtime_disable(jpeg->dev);
+
+ video_unregister_device(jpeg->vfd_decoder);
+ video_unregister_device(jpeg->vfd_encoder);
+ vb2_dma_contig_clear_max_seg_size(&pdev->dev);
+ v4l2_m2m_release(jpeg->m2m_dev);
+ v4l2_device_unregister(&jpeg->v4l2_dev);
+
+ if (!pm_runtime_status_suspended(&pdev->dev)) {
+ for (i = jpeg->variant->num_clocks - 1; i >= 0; i--)
+ clk_disable_unprepare(jpeg->clocks[i]);
+ }
+}
+
+#ifdef CONFIG_PM
+static int s5p_jpeg_runtime_suspend(struct device *dev)
+{
+ struct s5p_jpeg *jpeg = dev_get_drvdata(dev);
+ int i;
+
+ for (i = jpeg->variant->num_clocks - 1; i >= 0; i--)
+ clk_disable_unprepare(jpeg->clocks[i]);
+
+ return 0;
+}
+
+static int s5p_jpeg_runtime_resume(struct device *dev)
+{
+ struct s5p_jpeg *jpeg = dev_get_drvdata(dev);
+ unsigned long flags;
+ int i, ret;
+
+ for (i = 0; i < jpeg->variant->num_clocks; i++) {
+ ret = clk_prepare_enable(jpeg->clocks[i]);
+ if (ret) {
+ while (--i >= 0)
+ clk_disable_unprepare(jpeg->clocks[i]);
+ return ret;
+ }
+ }
+
+ spin_lock_irqsave(&jpeg->slock, flags);
+
+ /*
+ * JPEG IP allows storing two Huffman tables for each component.
+ * We fill table 0 for each component and do this here only
+ * for S5PC210 and Exynos3250 SoCs. Exynos4x12 and Exynos542x SoC
+ * require programming their Huffman tables each time the encoding
+ * process is initialized, and thus it is accomplished in the
+ * device_run callback of m2m_ops.
+ */
+ if (!jpeg->variant->htbl_reinit) {
+ s5p_jpeg_set_hdctbl(jpeg->regs);
+ s5p_jpeg_set_hdctblg(jpeg->regs);
+ s5p_jpeg_set_hactbl(jpeg->regs);
+ s5p_jpeg_set_hactblg(jpeg->regs);
+ }
+
+ spin_unlock_irqrestore(&jpeg->slock, flags);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops s5p_jpeg_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(s5p_jpeg_runtime_suspend, s5p_jpeg_runtime_resume,
+ NULL)
+};
+
+static struct s5p_jpeg_variant s5p_jpeg_drvdata = {
+ .version = SJPEG_S5P,
+ .jpeg_irq = s5p_jpeg_irq,
+ .m2m_ops = &s5p_jpeg_m2m_ops,
+ .fmt_ver_flag = SJPEG_FMT_FLAG_S5P,
+ .clk_names = {"jpeg"},
+ .num_clocks = 1,
+};
+
+static struct s5p_jpeg_variant exynos3250_jpeg_drvdata = {
+ .version = SJPEG_EXYNOS3250,
+ .jpeg_irq = exynos3250_jpeg_irq,
+ .m2m_ops = &exynos3250_jpeg_m2m_ops,
+ .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS3250,
+ .hw3250_compat = 1,
+ .clk_names = {"jpeg", "sclk"},
+ .num_clocks = 2,
+};
+
+static struct s5p_jpeg_variant exynos4_jpeg_drvdata = {
+ .version = SJPEG_EXYNOS4,
+ .jpeg_irq = exynos4_jpeg_irq,
+ .m2m_ops = &exynos4_jpeg_m2m_ops,
+ .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS4,
+ .htbl_reinit = 1,
+ .clk_names = {"jpeg"},
+ .num_clocks = 1,
+ .hw_ex4_compat = 1,
+};
+
+static struct s5p_jpeg_variant exynos5420_jpeg_drvdata = {
+ .version = SJPEG_EXYNOS5420,
+ .jpeg_irq = exynos3250_jpeg_irq, /* intentionally 3250 */
+ .m2m_ops = &exynos3250_jpeg_m2m_ops, /* intentionally 3250 */
+ .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS3250, /* intentionally 3250 */
+ .hw3250_compat = 1,
+ .htbl_reinit = 1,
+ .clk_names = {"jpeg"},
+ .num_clocks = 1,
+};
+
+static struct s5p_jpeg_variant exynos5433_jpeg_drvdata = {
+ .version = SJPEG_EXYNOS5433,
+ .jpeg_irq = exynos4_jpeg_irq,
+ .m2m_ops = &exynos4_jpeg_m2m_ops,
+ .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS4,
+ .htbl_reinit = 1,
+ .clk_names = {"pclk", "aclk", "aclk_xiu", "sclk"},
+ .num_clocks = 4,
+ .hw_ex4_compat = 1,
+};
+
+static const struct of_device_id samsung_jpeg_match[] = {
+ {
+ .compatible = "samsung,s5pv210-jpeg",
+ .data = &s5p_jpeg_drvdata,
+ }, {
+ .compatible = "samsung,exynos3250-jpeg",
+ .data = &exynos3250_jpeg_drvdata,
+ }, {
+ .compatible = "samsung,exynos4210-jpeg",
+ .data = &exynos4_jpeg_drvdata,
+ }, {
+ .compatible = "samsung,exynos4212-jpeg",
+ .data = &exynos4_jpeg_drvdata,
+ }, {
+ .compatible = "samsung,exynos5420-jpeg",
+ .data = &exynos5420_jpeg_drvdata,
+ }, {
+ .compatible = "samsung,exynos5433-jpeg",
+ .data = &exynos5433_jpeg_drvdata,
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, samsung_jpeg_match);
+
+static void *jpeg_get_drv_data(struct device *dev)
+{
+ struct s5p_jpeg_variant *driver_data = NULL;
+ const struct of_device_id *match;
+
+ if (!IS_ENABLED(CONFIG_OF) || !dev->of_node)
+ return &s5p_jpeg_drvdata;
+
+ match = of_match_node(samsung_jpeg_match, dev->of_node);
+
+ if (match)
+ driver_data = (struct s5p_jpeg_variant *)match->data;
+
+ return driver_data;
+}
+
+static struct platform_driver s5p_jpeg_driver = {
+ .probe = s5p_jpeg_probe,
+ .remove_new = s5p_jpeg_remove,
+ .driver = {
+ .of_match_table = samsung_jpeg_match,
+ .name = S5P_JPEG_M2M_NAME,
+ .pm = &s5p_jpeg_pm_ops,
+ },
+};
+
+module_platform_driver(s5p_jpeg_driver);
+
+MODULE_AUTHOR("Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>");
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Samsung JPEG codec driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h
new file mode 100644
index 0000000000..4b665a3b63
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h
@@ -0,0 +1,257 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#ifndef JPEG_CORE_H_
+#define JPEG_CORE_H_
+
+#include <linux/interrupt.h>
+#include <media/jpeg.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-ctrls.h>
+
+#define S5P_JPEG_M2M_NAME "s5p-jpeg"
+
+#define JPEG_MAX_CLOCKS 4
+
+/* JPEG compression quality setting */
+#define S5P_JPEG_COMPR_QUAL_BEST 0
+#define S5P_JPEG_COMPR_QUAL_WORST 3
+
+/* JPEG RGB to YCbCr conversion matrix coefficients */
+#define S5P_JPEG_COEF11 0x4d
+#define S5P_JPEG_COEF12 0x97
+#define S5P_JPEG_COEF13 0x1e
+#define S5P_JPEG_COEF21 0x2c
+#define S5P_JPEG_COEF22 0x57
+#define S5P_JPEG_COEF23 0x83
+#define S5P_JPEG_COEF31 0x83
+#define S5P_JPEG_COEF32 0x6e
+#define S5P_JPEG_COEF33 0x13
+
+#define EXYNOS3250_IRQ_TIMEOUT 0x10000000
+
+/* Flags that indicate a format can be used for capture/output */
+#define SJPEG_FMT_FLAG_ENC_CAPTURE (1 << 0)
+#define SJPEG_FMT_FLAG_ENC_OUTPUT (1 << 1)
+#define SJPEG_FMT_FLAG_DEC_CAPTURE (1 << 2)
+#define SJPEG_FMT_FLAG_DEC_OUTPUT (1 << 3)
+#define SJPEG_FMT_FLAG_S5P (1 << 4)
+#define SJPEG_FMT_FLAG_EXYNOS3250 (1 << 5)
+#define SJPEG_FMT_FLAG_EXYNOS4 (1 << 6)
+#define SJPEG_FMT_RGB (1 << 7)
+#define SJPEG_FMT_NON_RGB (1 << 8)
+
+#define S5P_JPEG_ENCODE 0
+#define S5P_JPEG_DECODE 1
+#define S5P_JPEG_DISABLE -1
+
+#define FMT_TYPE_OUTPUT 0
+#define FMT_TYPE_CAPTURE 1
+
+#define SJPEG_SUBSAMPLING_444 0x11
+#define SJPEG_SUBSAMPLING_422 0x21
+#define SJPEG_SUBSAMPLING_420 0x22
+
+#define S5P_JPEG_MAX_MARKER 4
+
+/* Version numbers */
+enum sjpeg_version {
+ SJPEG_S5P,
+ SJPEG_EXYNOS3250,
+ SJPEG_EXYNOS4,
+ SJPEG_EXYNOS5420,
+ SJPEG_EXYNOS5433,
+};
+
+enum exynos4_jpeg_result {
+ OK_ENC_OR_DEC,
+ ERR_PROT,
+ ERR_DEC_INVALID_FORMAT,
+ ERR_MULTI_SCAN,
+ ERR_FRAME,
+ ERR_UNKNOWN,
+};
+
+enum exynos4_jpeg_img_quality_level {
+ QUALITY_LEVEL_1 = 0, /* high */
+ QUALITY_LEVEL_2,
+ QUALITY_LEVEL_3,
+ QUALITY_LEVEL_4, /* low */
+};
+
+enum s5p_jpeg_ctx_state {
+ JPEGCTX_RUNNING = 0,
+ JPEGCTX_RESOLUTION_CHANGE,
+};
+
+/**
+ * struct s5p_jpeg - JPEG IP abstraction
+ * @lock: the mutex protecting this structure
+ * @slock: spinlock protecting the device contexts
+ * @v4l2_dev: v4l2 device for mem2mem mode
+ * @vfd_encoder: video device node for encoder mem2mem mode
+ * @vfd_decoder: video device node for decoder mem2mem mode
+ * @m2m_dev: v4l2 mem2mem device data
+ * @regs: JPEG IP registers mapping
+ * @irq: JPEG IP irq
+ * @irq_ret: JPEG IP irq result value
+ * @clocks: JPEG IP clock(s)
+ * @dev: JPEG IP struct device
+ * @variant: driver variant to be used
+ * @irq_status: interrupt flags set during single encode/decode
+ * operation
+ */
+struct s5p_jpeg {
+ struct mutex lock;
+ spinlock_t slock;
+
+ struct v4l2_device v4l2_dev;
+ struct video_device *vfd_encoder;
+ struct video_device *vfd_decoder;
+ struct v4l2_m2m_dev *m2m_dev;
+
+ void __iomem *regs;
+ unsigned int irq;
+ enum exynos4_jpeg_result irq_ret;
+ struct clk *clocks[JPEG_MAX_CLOCKS];
+ struct device *dev;
+ struct s5p_jpeg_variant *variant;
+ u32 irq_status;
+};
+
+struct s5p_jpeg_variant {
+ unsigned int version;
+ unsigned int fmt_ver_flag;
+ unsigned int hw3250_compat:1;
+ unsigned int htbl_reinit:1;
+ unsigned int hw_ex4_compat:1;
+ const struct v4l2_m2m_ops *m2m_ops;
+ irqreturn_t (*jpeg_irq)(int irq, void *priv);
+ const char *clk_names[JPEG_MAX_CLOCKS];
+ int num_clocks;
+};
+
+/**
+ * struct s5p_jpeg_fmt - driver's internal color format data
+ * @fourcc: the fourcc code, 0 if not applicable
+ * @depth: number of bits per pixel
+ * @colplanes: number of color planes (1 for packed formats)
+ * @memplanes: number of memory planes (1 for packed formats)
+ * @h_align: horizontal alignment order (align to 2^h_align)
+ * @v_align: vertical alignment order (align to 2^v_align)
+ * @subsampling:subsampling of a raw format or a JPEG
+ * @flags: flags describing format applicability
+ */
+struct s5p_jpeg_fmt {
+ u32 fourcc;
+ int depth;
+ int colplanes;
+ int memplanes;
+ int h_align;
+ int v_align;
+ int subsampling;
+ u32 flags;
+};
+
+/**
+ * struct s5p_jpeg_marker - collection of markers from jpeg header
+ * @marker: markers' positions relative to the buffer beginning
+ * @len: markers' payload lengths (without length field)
+ * @n: number of markers in collection
+ */
+struct s5p_jpeg_marker {
+ u32 marker[S5P_JPEG_MAX_MARKER];
+ u32 len[S5P_JPEG_MAX_MARKER];
+ u32 n;
+};
+
+/**
+ * struct s5p_jpeg_q_data - parameters of one queue
+ * @fmt: driver-specific format of this queue
+ * @w: image width
+ * @h: image height
+ * @sos: JPEG_MARKER_SOS's position relative to the buffer beginning
+ * @dht: JPEG_MARKER_DHT' positions relative to the buffer beginning
+ * @dqt: JPEG_MARKER_DQT' positions relative to the buffer beginning
+ * @sof: JPEG_MARKER_SOF0's position relative to the buffer beginning
+ * @sof_len: JPEG_MARKER_SOF0's payload length (without length field itself)
+ * @size: image buffer size in bytes
+ */
+struct s5p_jpeg_q_data {
+ struct s5p_jpeg_fmt *fmt;
+ u32 w;
+ u32 h;
+ u32 sos;
+ struct s5p_jpeg_marker dht;
+ struct s5p_jpeg_marker dqt;
+ u32 sof;
+ u32 sof_len;
+ u32 size;
+};
+
+/**
+ * struct s5p_jpeg_ctx - the device context data
+ * @jpeg: JPEG IP device for this context
+ * @mode: compression (encode) operation or decompression (decode)
+ * @compr_quality: destination image quality in compression (encode) mode
+ * @restart_interval: JPEG restart interval for JPEG encoding
+ * @subsampling: subsampling of a raw format or a JPEG
+ * @out_q: source (output) queue information
+ * @cap_q: destination (capture) queue queue information
+ * @scale_factor: scale factor for JPEG decoding
+ * @crop_rect: a rectangle representing crop area of the output buffer
+ * @fh: V4L2 file handle
+ * @hdr_parsed: set if header has been parsed during decompression
+ * @crop_altered: set if crop rectangle has been altered by the user space
+ * @ctrl_handler: controls handler
+ * @state: state of the context
+ */
+struct s5p_jpeg_ctx {
+ struct s5p_jpeg *jpeg;
+ unsigned int mode;
+ unsigned short compr_quality;
+ unsigned short restart_interval;
+ unsigned short subsampling;
+ struct s5p_jpeg_q_data out_q;
+ struct s5p_jpeg_q_data cap_q;
+ unsigned int scale_factor;
+ struct v4l2_rect crop_rect;
+ struct v4l2_fh fh;
+ bool hdr_parsed;
+ bool crop_altered;
+ struct v4l2_ctrl_handler ctrl_handler;
+ enum s5p_jpeg_ctx_state state;
+};
+
+/**
+ * struct s5p_jpeg_buffer - description of memory containing input JPEG data
+ * @size: buffer size
+ * @curr: current position in the buffer
+ * @data: pointer to the data
+ */
+struct s5p_jpeg_buffer {
+ unsigned long size;
+ unsigned long curr;
+ unsigned long data;
+};
+
+/**
+ * struct s5p_jpeg_addr - JPEG converter physical address set for DMA
+ * @y: luminance plane physical address
+ * @cb: Cb plane physical address
+ * @cr: Cr plane physical address
+ */
+struct s5p_jpeg_addr {
+ u32 y;
+ u32 cb;
+ u32 cr;
+};
+
+#endif /* JPEG_CORE_H */
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c
new file mode 100644
index 0000000000..637a5104d9
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* linux/drivers/media/platform/exynos3250-jpeg/jpeg-hw.h
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+
+#include <linux/io.h>
+#include <linux/videodev2.h>
+#include <linux/delay.h>
+
+#include "jpeg-core.h"
+#include "jpeg-regs.h"
+#include "jpeg-hw-exynos3250.h"
+
+void exynos3250_jpeg_reset(void __iomem *regs)
+{
+ u32 reg = 1;
+ int count = 1000;
+
+ writel(1, regs + EXYNOS3250_SW_RESET);
+ /* no other way but polling for when JPEG IP becomes operational */
+ while (reg != 0 && --count > 0) {
+ udelay(1);
+ cpu_relax();
+ reg = readl(regs + EXYNOS3250_SW_RESET);
+ }
+
+ reg = 0;
+ count = 1000;
+
+ while (reg != 1 && --count > 0) {
+ writel(1, regs + EXYNOS3250_JPGDRI);
+ udelay(1);
+ cpu_relax();
+ reg = readl(regs + EXYNOS3250_JPGDRI);
+ }
+
+ writel(0, regs + EXYNOS3250_JPGDRI);
+}
+
+void exynos3250_jpeg_poweron(void __iomem *regs)
+{
+ writel(EXYNOS3250_POWER_ON, regs + EXYNOS3250_JPGCLKCON);
+}
+
+void exynos3250_jpeg_set_dma_num(void __iomem *regs)
+{
+ writel(((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_WDMA_ISSUE_NUM_SHIFT) &
+ EXYNOS3250_WDMA_ISSUE_NUM_MASK) |
+ ((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_RDMA_ISSUE_NUM_SHIFT) &
+ EXYNOS3250_RDMA_ISSUE_NUM_MASK) |
+ ((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_ISSUE_GATHER_NUM_SHIFT) &
+ EXYNOS3250_ISSUE_GATHER_NUM_MASK),
+ regs + EXYNOS3250_DMA_ISSUE_NUM);
+}
+
+void exynos3250_jpeg_clk_set(void __iomem *base)
+{
+ u32 reg;
+
+ reg = readl(base + EXYNOS3250_JPGCMOD) & ~EXYNOS3250_HALF_EN_MASK;
+
+ writel(reg | EXYNOS3250_HALF_EN, base + EXYNOS3250_JPGCMOD);
+}
+
+void exynos3250_jpeg_input_raw_fmt(void __iomem *regs, unsigned int fmt)
+{
+ u32 reg;
+
+ reg = readl(regs + EXYNOS3250_JPGCMOD) &
+ EXYNOS3250_MODE_Y16_MASK;
+
+ switch (fmt) {
+ case V4L2_PIX_FMT_RGB32:
+ reg |= EXYNOS3250_MODE_SEL_ARGB8888;
+ break;
+ case V4L2_PIX_FMT_BGR32:
+ reg |= EXYNOS3250_MODE_SEL_ARGB8888 | EXYNOS3250_SRC_SWAP_RGB;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ reg |= EXYNOS3250_MODE_SEL_RGB565;
+ break;
+ case V4L2_PIX_FMT_RGB565X:
+ reg |= EXYNOS3250_MODE_SEL_RGB565 | EXYNOS3250_SRC_SWAP_RGB;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ reg |= EXYNOS3250_MODE_SEL_422_1P_LUM_CHR;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ reg |= EXYNOS3250_MODE_SEL_422_1P_LUM_CHR |
+ EXYNOS3250_SRC_SWAP_UV;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ reg |= EXYNOS3250_MODE_SEL_422_1P_CHR_LUM;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ reg |= EXYNOS3250_MODE_SEL_422_1P_CHR_LUM |
+ EXYNOS3250_SRC_SWAP_UV;
+ break;
+ case V4L2_PIX_FMT_NV12:
+ reg |= EXYNOS3250_MODE_SEL_420_2P | EXYNOS3250_SRC_NV12;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ reg |= EXYNOS3250_MODE_SEL_420_2P | EXYNOS3250_SRC_NV21;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ reg |= EXYNOS3250_MODE_SEL_420_3P;
+ break;
+ default:
+ break;
+
+ }
+
+ writel(reg, regs + EXYNOS3250_JPGCMOD);
+}
+
+void exynos3250_jpeg_set_y16(void __iomem *regs, bool y16)
+{
+ u32 reg;
+
+ reg = readl(regs + EXYNOS3250_JPGCMOD);
+ if (y16)
+ reg |= EXYNOS3250_MODE_Y16;
+ else
+ reg &= ~EXYNOS3250_MODE_Y16_MASK;
+ writel(reg, regs + EXYNOS3250_JPGCMOD);
+}
+
+void exynos3250_jpeg_proc_mode(void __iomem *regs, unsigned int mode)
+{
+ u32 reg, m;
+
+ if (mode == S5P_JPEG_ENCODE)
+ m = EXYNOS3250_PROC_MODE_COMPR;
+ else
+ m = EXYNOS3250_PROC_MODE_DECOMPR;
+ reg = readl(regs + EXYNOS3250_JPGMOD);
+ reg &= ~EXYNOS3250_PROC_MODE_MASK;
+ reg |= m;
+ writel(reg, regs + EXYNOS3250_JPGMOD);
+}
+
+void exynos3250_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode)
+{
+ u32 reg, m = 0;
+
+ switch (mode) {
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_444:
+ m = EXYNOS3250_SUBSAMPLING_MODE_444;
+ break;
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_422:
+ m = EXYNOS3250_SUBSAMPLING_MODE_422;
+ break;
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_420:
+ m = EXYNOS3250_SUBSAMPLING_MODE_420;
+ break;
+ }
+
+ reg = readl(regs + EXYNOS3250_JPGMOD);
+ reg &= ~EXYNOS3250_SUBSAMPLING_MODE_MASK;
+ reg |= m;
+ writel(reg, regs + EXYNOS3250_JPGMOD);
+}
+
+unsigned int exynos3250_jpeg_get_subsampling_mode(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_JPGMOD) &
+ EXYNOS3250_SUBSAMPLING_MODE_MASK;
+}
+
+void exynos3250_jpeg_dri(void __iomem *regs, unsigned int dri)
+{
+ u32 reg;
+
+ reg = dri & EXYNOS3250_JPGDRI_MASK;
+ writel(reg, regs + EXYNOS3250_JPGDRI);
+}
+
+void exynos3250_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n)
+{
+ unsigned long reg;
+
+ reg = readl(regs + EXYNOS3250_QHTBL);
+ reg &= ~EXYNOS3250_QT_NUM_MASK(t);
+ reg |= (n << EXYNOS3250_QT_NUM_SHIFT(t)) &
+ EXYNOS3250_QT_NUM_MASK(t);
+ writel(reg, regs + EXYNOS3250_QHTBL);
+}
+
+void exynos3250_jpeg_htbl_ac(void __iomem *regs, unsigned int t)
+{
+ unsigned long reg;
+
+ reg = readl(regs + EXYNOS3250_QHTBL);
+ reg &= ~EXYNOS3250_HT_NUM_AC_MASK(t);
+ /* this driver uses table 0 for all color components */
+ reg |= (0 << EXYNOS3250_HT_NUM_AC_SHIFT(t)) &
+ EXYNOS3250_HT_NUM_AC_MASK(t);
+ writel(reg, regs + EXYNOS3250_QHTBL);
+}
+
+void exynos3250_jpeg_htbl_dc(void __iomem *regs, unsigned int t)
+{
+ unsigned long reg;
+
+ reg = readl(regs + EXYNOS3250_QHTBL);
+ reg &= ~EXYNOS3250_HT_NUM_DC_MASK(t);
+ /* this driver uses table 0 for all color components */
+ reg |= (0 << EXYNOS3250_HT_NUM_DC_SHIFT(t)) &
+ EXYNOS3250_HT_NUM_DC_MASK(t);
+ writel(reg, regs + EXYNOS3250_QHTBL);
+}
+
+void exynos3250_jpeg_set_y(void __iomem *regs, unsigned int y)
+{
+ u32 reg;
+
+ reg = y & EXYNOS3250_JPGY_MASK;
+ writel(reg, regs + EXYNOS3250_JPGY);
+}
+
+void exynos3250_jpeg_set_x(void __iomem *regs, unsigned int x)
+{
+ u32 reg;
+
+ reg = x & EXYNOS3250_JPGX_MASK;
+ writel(reg, regs + EXYNOS3250_JPGX);
+}
+
+#if 0 /* Currently unused */
+unsigned int exynos3250_jpeg_get_y(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_JPGY);
+}
+
+unsigned int exynos3250_jpeg_get_x(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_JPGX);
+}
+#endif
+
+void exynos3250_jpeg_interrupts_enable(void __iomem *regs)
+{
+ u32 reg;
+
+ reg = readl(regs + EXYNOS3250_JPGINTSE);
+ reg |= (EXYNOS3250_JPEG_DONE_EN |
+ EXYNOS3250_WDMA_DONE_EN |
+ EXYNOS3250_RDMA_DONE_EN |
+ EXYNOS3250_ENC_STREAM_INT_EN |
+ EXYNOS3250_CORE_DONE_EN |
+ EXYNOS3250_ERR_INT_EN |
+ EXYNOS3250_HEAD_INT_EN);
+ writel(reg, regs + EXYNOS3250_JPGINTSE);
+}
+
+void exynos3250_jpeg_enc_stream_bound(void __iomem *regs, unsigned int size)
+{
+ u32 reg;
+
+ reg = size & EXYNOS3250_ENC_STREAM_BOUND_MASK;
+ writel(reg, regs + EXYNOS3250_ENC_STREAM_BOUND);
+}
+
+void exynos3250_jpeg_output_raw_fmt(void __iomem *regs, unsigned int fmt)
+{
+ u32 reg;
+
+ switch (fmt) {
+ case V4L2_PIX_FMT_RGB32:
+ reg = EXYNOS3250_OUT_FMT_ARGB8888;
+ break;
+ case V4L2_PIX_FMT_BGR32:
+ reg = EXYNOS3250_OUT_FMT_ARGB8888 | EXYNOS3250_OUT_SWAP_RGB;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ reg = EXYNOS3250_OUT_FMT_RGB565;
+ break;
+ case V4L2_PIX_FMT_RGB565X:
+ reg = EXYNOS3250_OUT_FMT_RGB565 | EXYNOS3250_OUT_SWAP_RGB;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ reg = EXYNOS3250_OUT_FMT_422_1P_LUM_CHR;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ reg = EXYNOS3250_OUT_FMT_422_1P_LUM_CHR |
+ EXYNOS3250_OUT_SWAP_UV;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ reg = EXYNOS3250_OUT_FMT_422_1P_CHR_LUM;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ reg = EXYNOS3250_OUT_FMT_422_1P_CHR_LUM |
+ EXYNOS3250_OUT_SWAP_UV;
+ break;
+ case V4L2_PIX_FMT_NV12:
+ reg = EXYNOS3250_OUT_FMT_420_2P | EXYNOS3250_OUT_NV12;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ reg = EXYNOS3250_OUT_FMT_420_2P | EXYNOS3250_OUT_NV21;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ reg = EXYNOS3250_OUT_FMT_420_3P;
+ break;
+ default:
+ reg = 0;
+ break;
+ }
+
+ writel(reg, regs + EXYNOS3250_OUTFORM);
+}
+
+void exynos3250_jpeg_jpgadr(void __iomem *regs, unsigned int addr)
+{
+ writel(addr, regs + EXYNOS3250_JPG_JPGADR);
+}
+
+void exynos3250_jpeg_imgadr(void __iomem *regs, struct s5p_jpeg_addr *img_addr)
+{
+ writel(img_addr->y, regs + EXYNOS3250_LUMA_BASE);
+ writel(img_addr->cb, regs + EXYNOS3250_CHROMA_BASE);
+ writel(img_addr->cr, regs + EXYNOS3250_CHROMA_CR_BASE);
+}
+
+void exynos3250_jpeg_stride(void __iomem *regs, unsigned int img_fmt,
+ unsigned int width)
+{
+ u32 reg_luma = 0, reg_cr = 0, reg_cb = 0;
+
+ switch (img_fmt) {
+ case V4L2_PIX_FMT_RGB32:
+ reg_luma = 4 * width;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ case V4L2_PIX_FMT_RGB565X:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ reg_luma = 2 * width;
+ break;
+ case V4L2_PIX_FMT_NV12:
+ case V4L2_PIX_FMT_NV21:
+ reg_luma = width;
+ reg_cb = reg_luma;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ reg_luma = width;
+ reg_cb = reg_cr = reg_luma / 2;
+ break;
+ default:
+ break;
+ }
+
+ writel(reg_luma, regs + EXYNOS3250_LUMA_STRIDE);
+ writel(reg_cb, regs + EXYNOS3250_CHROMA_STRIDE);
+ writel(reg_cr, regs + EXYNOS3250_CHROMA_CR_STRIDE);
+}
+
+void exynos3250_jpeg_offset(void __iomem *regs, unsigned int x_offset,
+ unsigned int y_offset)
+{
+ u32 reg;
+
+ reg = (y_offset << EXYNOS3250_LUMA_YY_OFFSET_SHIFT) &
+ EXYNOS3250_LUMA_YY_OFFSET_MASK;
+ reg |= (x_offset << EXYNOS3250_LUMA_YX_OFFSET_SHIFT) &
+ EXYNOS3250_LUMA_YX_OFFSET_MASK;
+
+ writel(reg, regs + EXYNOS3250_LUMA_XY_OFFSET);
+
+ reg = (y_offset << EXYNOS3250_CHROMA_YY_OFFSET_SHIFT) &
+ EXYNOS3250_CHROMA_YY_OFFSET_MASK;
+ reg |= (x_offset << EXYNOS3250_CHROMA_YX_OFFSET_SHIFT) &
+ EXYNOS3250_CHROMA_YX_OFFSET_MASK;
+
+ writel(reg, regs + EXYNOS3250_CHROMA_XY_OFFSET);
+
+ reg = (y_offset << EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT) &
+ EXYNOS3250_CHROMA_CR_YY_OFFSET_MASK;
+ reg |= (x_offset << EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT) &
+ EXYNOS3250_CHROMA_CR_YX_OFFSET_MASK;
+
+ writel(reg, regs + EXYNOS3250_CHROMA_CR_XY_OFFSET);
+}
+
+void exynos3250_jpeg_coef(void __iomem *base, unsigned int mode)
+{
+ if (mode == S5P_JPEG_ENCODE) {
+ writel(EXYNOS3250_JPEG_ENC_COEF1,
+ base + EXYNOS3250_JPG_COEF(1));
+ writel(EXYNOS3250_JPEG_ENC_COEF2,
+ base + EXYNOS3250_JPG_COEF(2));
+ writel(EXYNOS3250_JPEG_ENC_COEF3,
+ base + EXYNOS3250_JPG_COEF(3));
+ } else {
+ writel(EXYNOS3250_JPEG_DEC_COEF1,
+ base + EXYNOS3250_JPG_COEF(1));
+ writel(EXYNOS3250_JPEG_DEC_COEF2,
+ base + EXYNOS3250_JPG_COEF(2));
+ writel(EXYNOS3250_JPEG_DEC_COEF3,
+ base + EXYNOS3250_JPG_COEF(3));
+ }
+}
+
+void exynos3250_jpeg_start(void __iomem *regs)
+{
+ writel(1, regs + EXYNOS3250_JSTART);
+}
+
+void exynos3250_jpeg_rstart(void __iomem *regs)
+{
+ writel(1, regs + EXYNOS3250_JRSTART);
+}
+
+unsigned int exynos3250_jpeg_get_int_status(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_JPGINTST);
+}
+
+void exynos3250_jpeg_clear_int_status(void __iomem *regs,
+ unsigned int value)
+{
+ writel(value, regs + EXYNOS3250_JPGINTST);
+}
+
+unsigned int exynos3250_jpeg_operating(void __iomem *regs)
+{
+ return readl(regs + S5P_JPGOPR) & EXYNOS3250_JPGOPR_MASK;
+}
+
+unsigned int exynos3250_jpeg_compressed_size(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_JPGCNT) & EXYNOS3250_JPGCNT_MASK;
+}
+
+void exynos3250_jpeg_dec_stream_size(void __iomem *regs,
+ unsigned int size)
+{
+ writel(size & EXYNOS3250_DEC_STREAM_MASK,
+ regs + EXYNOS3250_DEC_STREAM_SIZE);
+}
+
+void exynos3250_jpeg_dec_scaling_ratio(void __iomem *regs,
+ unsigned int sratio)
+{
+ switch (sratio) {
+ case 1:
+ default:
+ sratio = EXYNOS3250_DEC_SCALE_FACTOR_8_8;
+ break;
+ case 2:
+ sratio = EXYNOS3250_DEC_SCALE_FACTOR_4_8;
+ break;
+ case 4:
+ sratio = EXYNOS3250_DEC_SCALE_FACTOR_2_8;
+ break;
+ case 8:
+ sratio = EXYNOS3250_DEC_SCALE_FACTOR_1_8;
+ break;
+ }
+
+ writel(sratio & EXYNOS3250_DEC_SCALE_FACTOR_MASK,
+ regs + EXYNOS3250_DEC_SCALING_RATIO);
+}
+
+void exynos3250_jpeg_set_timer(void __iomem *regs, unsigned int time_value)
+{
+ time_value &= EXYNOS3250_TIMER_INIT_MASK;
+
+ writel(EXYNOS3250_TIMER_INT_STAT | time_value,
+ regs + EXYNOS3250_TIMER_SE);
+}
+
+unsigned int exynos3250_jpeg_get_timer_status(void __iomem *regs)
+{
+ return readl(regs + EXYNOS3250_TIMER_ST);
+}
+
+void exynos3250_jpeg_clear_timer_status(void __iomem *regs)
+{
+ writel(EXYNOS3250_TIMER_INT_STAT, regs + EXYNOS3250_TIMER_ST);
+}
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h
new file mode 100644
index 0000000000..15af928fad
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+#ifndef JPEG_HW_EXYNOS3250_H_
+#define JPEG_HW_EXYNOS3250_H_
+
+#include <linux/io.h>
+#include <linux/videodev2.h>
+
+#include "jpeg-regs.h"
+
+void exynos3250_jpeg_reset(void __iomem *regs);
+void exynos3250_jpeg_poweron(void __iomem *regs);
+void exynos3250_jpeg_set_dma_num(void __iomem *regs);
+void exynos3250_jpeg_clk_set(void __iomem *base);
+void exynos3250_jpeg_input_raw_fmt(void __iomem *regs, unsigned int fmt);
+void exynos3250_jpeg_output_raw_fmt(void __iomem *regs, unsigned int fmt);
+void exynos3250_jpeg_set_y16(void __iomem *regs, bool y16);
+void exynos3250_jpeg_proc_mode(void __iomem *regs, unsigned int mode);
+void exynos3250_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode);
+unsigned int exynos3250_jpeg_get_subsampling_mode(void __iomem *regs);
+void exynos3250_jpeg_dri(void __iomem *regs, unsigned int dri);
+void exynos3250_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n);
+void exynos3250_jpeg_htbl_ac(void __iomem *regs, unsigned int t);
+void exynos3250_jpeg_htbl_dc(void __iomem *regs, unsigned int t);
+void exynos3250_jpeg_set_y(void __iomem *regs, unsigned int y);
+void exynos3250_jpeg_set_x(void __iomem *regs, unsigned int x);
+void exynos3250_jpeg_interrupts_enable(void __iomem *regs);
+void exynos3250_jpeg_enc_stream_bound(void __iomem *regs, unsigned int size);
+void exynos3250_jpeg_outform_raw(void __iomem *regs, unsigned long format);
+void exynos3250_jpeg_jpgadr(void __iomem *regs, unsigned int addr);
+void exynos3250_jpeg_imgadr(void __iomem *regs, struct s5p_jpeg_addr *img_addr);
+void exynos3250_jpeg_stride(void __iomem *regs, unsigned int img_fmt,
+ unsigned int width);
+void exynos3250_jpeg_offset(void __iomem *regs, unsigned int x_offset,
+ unsigned int y_offset);
+void exynos3250_jpeg_coef(void __iomem *base, unsigned int mode);
+void exynos3250_jpeg_start(void __iomem *regs);
+void exynos3250_jpeg_rstart(void __iomem *regs);
+unsigned int exynos3250_jpeg_get_int_status(void __iomem *regs);
+void exynos3250_jpeg_clear_int_status(void __iomem *regs,
+ unsigned int value);
+unsigned int exynos3250_jpeg_operating(void __iomem *regs);
+unsigned int exynos3250_jpeg_compressed_size(void __iomem *regs);
+void exynos3250_jpeg_dec_stream_size(void __iomem *regs, unsigned int size);
+void exynos3250_jpeg_dec_scaling_ratio(void __iomem *regs, unsigned int sratio);
+void exynos3250_jpeg_set_timer(void __iomem *regs, unsigned int time_value);
+unsigned int exynos3250_jpeg_get_timer_status(void __iomem *regs);
+void exynos3250_jpeg_set_timer_status(void __iomem *regs);
+void exynos3250_jpeg_clear_timer_status(void __iomem *regs);
+
+#endif /* JPEG_HW_EXYNOS3250_H_ */
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c
new file mode 100644
index 0000000000..0828cfa783
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * Register interface file for JPEG driver on Exynos4x12.
+ */
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include "jpeg-core.h"
+#include "jpeg-hw-exynos4.h"
+#include "jpeg-regs.h"
+
+void exynos4_jpeg_sw_reset(void __iomem *base)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_JPEG_CNTL_REG);
+ writel(reg & ~(EXYNOS4_DEC_MODE | EXYNOS4_ENC_MODE),
+ base + EXYNOS4_JPEG_CNTL_REG);
+
+ reg = readl(base + EXYNOS4_JPEG_CNTL_REG);
+ writel(reg & ~EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG);
+
+ udelay(100);
+
+ writel(reg | EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG);
+}
+
+void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_JPEG_CNTL_REG);
+ /* set exynos4_jpeg mod register */
+ if (mode == S5P_JPEG_DECODE) {
+ writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) |
+ EXYNOS4_DEC_MODE,
+ base + EXYNOS4_JPEG_CNTL_REG);
+ } else if (mode == S5P_JPEG_ENCODE) {/* encode */
+ writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) |
+ EXYNOS4_ENC_MODE,
+ base + EXYNOS4_JPEG_CNTL_REG);
+ } else { /* disable both */
+ writel(reg & EXYNOS4_ENC_DEC_MODE_MASK,
+ base + EXYNOS4_JPEG_CNTL_REG);
+ }
+}
+
+void __exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt,
+ unsigned int version)
+{
+ unsigned int reg;
+ unsigned int exynos4_swap_chroma_cbcr;
+ unsigned int exynos4_swap_chroma_crcb;
+
+ if (version == SJPEG_EXYNOS4) {
+ exynos4_swap_chroma_cbcr = EXYNOS4_SWAP_CHROMA_CBCR;
+ exynos4_swap_chroma_crcb = EXYNOS4_SWAP_CHROMA_CRCB;
+ } else {
+ exynos4_swap_chroma_cbcr = EXYNOS5433_SWAP_CHROMA_CBCR;
+ exynos4_swap_chroma_crcb = EXYNOS5433_SWAP_CHROMA_CRCB;
+ }
+
+ reg = readl(base + EXYNOS4_IMG_FMT_REG) &
+ EXYNOS4_ENC_IN_FMT_MASK; /* clear except enc format */
+
+ switch (img_fmt) {
+ case V4L2_PIX_FMT_GREY:
+ reg = reg | EXYNOS4_ENC_GRAY_IMG | EXYNOS4_GRAY_IMG_IP;
+ break;
+ case V4L2_PIX_FMT_RGB32:
+ reg = reg | EXYNOS4_ENC_RGB_IMG |
+ EXYNOS4_RGB_IP_RGB_32BIT_IMG;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ reg = reg | EXYNOS4_ENC_RGB_IMG |
+ EXYNOS4_RGB_IP_RGB_16BIT_IMG;
+ break;
+ case V4L2_PIX_FMT_NV24:
+ reg = reg | EXYNOS4_ENC_YUV_444_IMG |
+ EXYNOS4_YUV_444_IP_YUV_444_2P_IMG |
+ exynos4_swap_chroma_cbcr;
+ break;
+ case V4L2_PIX_FMT_NV42:
+ reg = reg | EXYNOS4_ENC_YUV_444_IMG |
+ EXYNOS4_YUV_444_IP_YUV_444_2P_IMG |
+ exynos4_swap_chroma_crcb;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ reg = reg | EXYNOS4_DEC_YUV_422_IMG |
+ EXYNOS4_YUV_422_IP_YUV_422_1P_IMG |
+ exynos4_swap_chroma_cbcr;
+ break;
+
+ case V4L2_PIX_FMT_YVYU:
+ reg = reg | EXYNOS4_DEC_YUV_422_IMG |
+ EXYNOS4_YUV_422_IP_YUV_422_1P_IMG |
+ exynos4_swap_chroma_crcb;
+ break;
+ case V4L2_PIX_FMT_NV16:
+ reg = reg | EXYNOS4_DEC_YUV_422_IMG |
+ EXYNOS4_YUV_422_IP_YUV_422_2P_IMG |
+ exynos4_swap_chroma_cbcr;
+ break;
+ case V4L2_PIX_FMT_NV61:
+ reg = reg | EXYNOS4_DEC_YUV_422_IMG |
+ EXYNOS4_YUV_422_IP_YUV_422_2P_IMG |
+ exynos4_swap_chroma_crcb;
+ break;
+ case V4L2_PIX_FMT_NV12:
+ reg = reg | EXYNOS4_DEC_YUV_420_IMG |
+ EXYNOS4_YUV_420_IP_YUV_420_2P_IMG |
+ exynos4_swap_chroma_cbcr;
+ break;
+ case V4L2_PIX_FMT_NV21:
+ reg = reg | EXYNOS4_DEC_YUV_420_IMG |
+ EXYNOS4_YUV_420_IP_YUV_420_2P_IMG |
+ exynos4_swap_chroma_crcb;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ reg = reg | EXYNOS4_DEC_YUV_420_IMG |
+ EXYNOS4_YUV_420_IP_YUV_420_3P_IMG |
+ exynos4_swap_chroma_cbcr;
+ break;
+ default:
+ break;
+
+ }
+
+ writel(reg, base + EXYNOS4_IMG_FMT_REG);
+}
+
+void __exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt,
+ unsigned int version)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_IMG_FMT_REG) &
+ ~(version == SJPEG_EXYNOS4 ? EXYNOS4_ENC_FMT_MASK :
+ EXYNOS5433_ENC_FMT_MASK); /* clear enc format */
+
+ switch (out_fmt) {
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY:
+ reg = reg | EXYNOS4_ENC_FMT_GRAY;
+ break;
+
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_444:
+ reg = reg | EXYNOS4_ENC_FMT_YUV_444;
+ break;
+
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_422:
+ reg = reg | EXYNOS4_ENC_FMT_YUV_422;
+ break;
+
+ case V4L2_JPEG_CHROMA_SUBSAMPLING_420:
+ reg = reg | EXYNOS4_ENC_FMT_YUV_420;
+ break;
+
+ default:
+ break;
+ }
+
+ writel(reg, base + EXYNOS4_IMG_FMT_REG);
+}
+
+void exynos4_jpeg_set_interrupt(void __iomem *base, unsigned int version)
+{
+ unsigned int reg;
+
+ if (version == SJPEG_EXYNOS4) {
+ reg = readl(base + EXYNOS4_INT_EN_REG) & ~EXYNOS4_INT_EN_MASK;
+ writel(reg | EXYNOS4_INT_EN_ALL, base + EXYNOS4_INT_EN_REG);
+ } else {
+ reg = readl(base + EXYNOS4_INT_EN_REG) &
+ ~EXYNOS5433_INT_EN_MASK;
+ writel(reg | EXYNOS5433_INT_EN_ALL, base + EXYNOS4_INT_EN_REG);
+ }
+}
+
+unsigned int exynos4_jpeg_get_int_status(void __iomem *base)
+{
+ return readl(base + EXYNOS4_INT_STATUS_REG);
+}
+
+unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base)
+{
+ return readl(base + EXYNOS4_FIFO_STATUS_REG);
+}
+
+void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~EXYNOS4_HUF_TBL_EN;
+
+ if (value == 1)
+ writel(reg | EXYNOS4_HUF_TBL_EN,
+ base + EXYNOS4_JPEG_CNTL_REG);
+ else
+ writel(reg & ~EXYNOS4_HUF_TBL_EN,
+ base + EXYNOS4_JPEG_CNTL_REG);
+}
+
+void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~(EXYNOS4_SYS_INT_EN);
+
+ if (value == 1)
+ writel(reg | EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG);
+ else
+ writel(reg & ~EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG);
+}
+
+void exynos4_jpeg_set_stream_buf_address(void __iomem *base,
+ unsigned int address)
+{
+ writel(address, base + EXYNOS4_OUT_MEM_BASE_REG);
+}
+
+void exynos4_jpeg_set_stream_size(void __iomem *base,
+ unsigned int x_value, unsigned int y_value)
+{
+ writel(0x0, base + EXYNOS4_JPEG_IMG_SIZE_REG); /* clear */
+ writel(EXYNOS4_X_SIZE(x_value) | EXYNOS4_Y_SIZE(y_value),
+ base + EXYNOS4_JPEG_IMG_SIZE_REG);
+}
+
+void exynos4_jpeg_set_frame_buf_address(void __iomem *base,
+ struct s5p_jpeg_addr *exynos4_jpeg_addr)
+{
+ writel(exynos4_jpeg_addr->y, base + EXYNOS4_IMG_BA_PLANE_1_REG);
+ writel(exynos4_jpeg_addr->cb, base + EXYNOS4_IMG_BA_PLANE_2_REG);
+ writel(exynos4_jpeg_addr->cr, base + EXYNOS4_IMG_BA_PLANE_3_REG);
+}
+
+void exynos4_jpeg_set_encode_tbl_select(void __iomem *base,
+ enum exynos4_jpeg_img_quality_level level)
+{
+ unsigned int reg;
+
+ reg = EXYNOS4_Q_TBL_COMP1_0 | EXYNOS4_Q_TBL_COMP2_1 |
+ EXYNOS4_Q_TBL_COMP3_1 |
+ EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 |
+ EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 |
+ EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1;
+
+ writel(reg, base + EXYNOS4_TBL_SEL_REG);
+}
+
+void exynos4_jpeg_set_dec_components(void __iomem *base, int n)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_TBL_SEL_REG);
+
+ reg |= EXYNOS4_NF(n);
+ writel(reg, base + EXYNOS4_TBL_SEL_REG);
+}
+
+void exynos4_jpeg_select_dec_q_tbl(void __iomem *base, char c, char x)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_TBL_SEL_REG);
+
+ reg |= EXYNOS4_Q_TBL_COMP(c, x);
+ writel(reg, base + EXYNOS4_TBL_SEL_REG);
+}
+
+void exynos4_jpeg_select_dec_h_tbl(void __iomem *base, char c, char x)
+{
+ unsigned int reg;
+
+ reg = readl(base + EXYNOS4_TBL_SEL_REG);
+
+ reg |= EXYNOS4_HUFF_TBL_COMP(c, x);
+ writel(reg, base + EXYNOS4_TBL_SEL_REG);
+}
+
+void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt)
+{
+ if (fmt == V4L2_PIX_FMT_GREY)
+ writel(0xd2, base + EXYNOS4_HUFF_CNT_REG);
+ else
+ writel(0x1a2, base + EXYNOS4_HUFF_CNT_REG);
+}
+
+unsigned int exynos4_jpeg_get_stream_size(void __iomem *base)
+{
+ return readl(base + EXYNOS4_BITSTREAM_SIZE_REG);
+}
+
+void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size)
+{
+ writel(size, base + EXYNOS4_BITSTREAM_SIZE_REG);
+}
+
+void exynos4_jpeg_get_frame_size(void __iomem *base,
+ unsigned int *width, unsigned int *height)
+{
+ *width = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) &
+ EXYNOS4_DECODED_SIZE_MASK);
+ *height = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) >> 16) &
+ EXYNOS4_DECODED_SIZE_MASK;
+}
+
+unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base)
+{
+ return readl(base + EXYNOS4_DECODE_IMG_FMT_REG) &
+ EXYNOS4_JPEG_DECODED_IMG_FMT_MASK;
+}
+
+void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size)
+{
+ writel(size, base + EXYNOS4_INT_TIMER_COUNT_REG);
+}
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h
new file mode 100644
index 0000000000..3e28875269
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ *
+ * Header file of the register interface for JPEG driver on Exynos4x12.
+*/
+
+#ifndef JPEG_HW_EXYNOS4_H_
+#define JPEG_HW_EXYNOS4_H_
+
+void exynos4_jpeg_sw_reset(void __iomem *base);
+void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode);
+void __exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt,
+ unsigned int version);
+void __exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt,
+ unsigned int version);
+void exynos4_jpeg_set_enc_tbl(void __iomem *base);
+void exynos4_jpeg_set_interrupt(void __iomem *base, unsigned int version);
+unsigned int exynos4_jpeg_get_int_status(void __iomem *base);
+void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value);
+void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value);
+void exynos4_jpeg_set_stream_buf_address(void __iomem *base,
+ unsigned int address);
+void exynos4_jpeg_set_stream_size(void __iomem *base,
+ unsigned int x_value, unsigned int y_value);
+void exynos4_jpeg_set_frame_buf_address(void __iomem *base,
+ struct s5p_jpeg_addr *jpeg_addr);
+void exynos4_jpeg_set_encode_tbl_select(void __iomem *base,
+ enum exynos4_jpeg_img_quality_level level);
+void exynos4_jpeg_set_dec_components(void __iomem *base, int n);
+void exynos4_jpeg_select_dec_q_tbl(void __iomem *base, char c, char x);
+void exynos4_jpeg_select_dec_h_tbl(void __iomem *base, char c, char x);
+void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt);
+void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size);
+unsigned int exynos4_jpeg_get_stream_size(void __iomem *base);
+void exynos4_jpeg_get_frame_size(void __iomem *base,
+ unsigned int *width, unsigned int *height);
+unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base);
+unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base);
+void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size);
+
+#endif /* JPEG_HW_EXYNOS4_H_ */
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c
new file mode 100644
index 0000000000..33e6e85dfd
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+
+#include <linux/io.h>
+#include <linux/videodev2.h>
+
+#include "jpeg-core.h"
+#include "jpeg-regs.h"
+#include "jpeg-hw-s5p.h"
+
+void s5p_jpeg_reset(void __iomem *regs)
+{
+ unsigned long reg;
+
+ writel(1, regs + S5P_JPG_SW_RESET);
+ reg = readl(regs + S5P_JPG_SW_RESET);
+ /* no other way but polling for when JPEG IP becomes operational */
+ while (reg != 0) {
+ cpu_relax();
+ reg = readl(regs + S5P_JPG_SW_RESET);
+ }
+}
+
+void s5p_jpeg_poweron(void __iomem *regs)
+{
+ writel(S5P_POWER_ON, regs + S5P_JPGCLKCON);
+}
+
+void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode)
+{
+ unsigned long reg, m;
+
+ m = S5P_MOD_SEL_565;
+ if (mode == S5P_JPEG_RAW_IN_565)
+ m = S5P_MOD_SEL_565;
+ else if (mode == S5P_JPEG_RAW_IN_422)
+ m = S5P_MOD_SEL_422;
+
+ reg = readl(regs + S5P_JPGCMOD);
+ reg &= ~S5P_MOD_SEL_MASK;
+ reg |= m;
+ writel(reg, regs + S5P_JPGCMOD);
+}
+
+void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode)
+{
+ unsigned long reg, m;
+
+ if (mode == S5P_JPEG_ENCODE)
+ m = S5P_PROC_MODE_COMPR;
+ else
+ m = S5P_PROC_MODE_DECOMPR;
+ reg = readl(regs + S5P_JPGMOD);
+ reg &= ~S5P_PROC_MODE_MASK;
+ reg |= m;
+ writel(reg, regs + S5P_JPGMOD);
+}
+
+void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode)
+{
+ unsigned long reg, m;
+
+ if (mode == V4L2_JPEG_CHROMA_SUBSAMPLING_420)
+ m = S5P_SUBSAMPLING_MODE_420;
+ else
+ m = S5P_SUBSAMPLING_MODE_422;
+
+ reg = readl(regs + S5P_JPGMOD);
+ reg &= ~S5P_SUBSAMPLING_MODE_MASK;
+ reg |= m;
+ writel(reg, regs + S5P_JPGMOD);
+}
+
+unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs)
+{
+ return readl(regs + S5P_JPGMOD) & S5P_SUBSAMPLING_MODE_MASK;
+}
+
+void s5p_jpeg_dri(void __iomem *regs, unsigned int dri)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGDRI_U);
+ reg &= ~0xff;
+ reg |= (dri >> 8) & 0xff;
+ writel(reg, regs + S5P_JPGDRI_U);
+
+ reg = readl(regs + S5P_JPGDRI_L);
+ reg &= ~0xff;
+ reg |= dri & 0xff;
+ writel(reg, regs + S5P_JPGDRI_L);
+}
+
+void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_QTBL);
+ reg &= ~S5P_QT_NUMt_MASK(t);
+ reg |= (n << S5P_QT_NUMt_SHIFT(t)) & S5P_QT_NUMt_MASK(t);
+ writel(reg, regs + S5P_JPG_QTBL);
+}
+
+void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_HTBL);
+ reg &= ~S5P_HT_NUMt_AC_MASK(t);
+ /* this driver uses table 0 for all color components */
+ reg |= (0 << S5P_HT_NUMt_AC_SHIFT(t)) & S5P_HT_NUMt_AC_MASK(t);
+ writel(reg, regs + S5P_JPG_HTBL);
+}
+
+void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_HTBL);
+ reg &= ~S5P_HT_NUMt_DC_MASK(t);
+ /* this driver uses table 0 for all color components */
+ reg |= (0 << S5P_HT_NUMt_DC_SHIFT(t)) & S5P_HT_NUMt_DC_MASK(t);
+ writel(reg, regs + S5P_JPG_HTBL);
+}
+
+void s5p_jpeg_y(void __iomem *regs, unsigned int y)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGY_U);
+ reg &= ~0xff;
+ reg |= (y >> 8) & 0xff;
+ writel(reg, regs + S5P_JPGY_U);
+
+ reg = readl(regs + S5P_JPGY_L);
+ reg &= ~0xff;
+ reg |= y & 0xff;
+ writel(reg, regs + S5P_JPGY_L);
+}
+
+void s5p_jpeg_x(void __iomem *regs, unsigned int x)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGX_U);
+ reg &= ~0xff;
+ reg |= (x >> 8) & 0xff;
+ writel(reg, regs + S5P_JPGX_U);
+
+ reg = readl(regs + S5P_JPGX_L);
+ reg &= ~0xff;
+ reg |= x & 0xff;
+ writel(reg, regs + S5P_JPGX_L);
+}
+
+void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGINTSE);
+ reg &= ~S5P_RSTm_INT_EN_MASK;
+ if (enable)
+ reg |= S5P_RSTm_INT_EN;
+ writel(reg, regs + S5P_JPGINTSE);
+}
+
+void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGINTSE);
+ reg &= ~S5P_DATA_NUM_INT_EN_MASK;
+ if (enable)
+ reg |= S5P_DATA_NUM_INT_EN;
+ writel(reg, regs + S5P_JPGINTSE);
+}
+
+void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPGINTSE);
+ reg &= ~S5P_FINAL_MCU_NUM_INT_EN_MASK;
+ if (enbl)
+ reg |= S5P_FINAL_MCU_NUM_INT_EN;
+ writel(reg, regs + S5P_JPGINTSE);
+}
+
+int s5p_jpeg_timer_stat(void __iomem *regs)
+{
+ return (int)((readl(regs + S5P_JPG_TIMER_ST) & S5P_TIMER_INT_STAT_MASK)
+ >> S5P_TIMER_INT_STAT_SHIFT);
+}
+
+void s5p_jpeg_clear_timer_stat(void __iomem *regs)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_TIMER_SE);
+ reg &= ~S5P_TIMER_INT_STAT_MASK;
+ writel(reg, regs + S5P_JPG_TIMER_SE);
+}
+
+void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_ENC_STREAM_INTSE);
+ reg &= ~S5P_ENC_STREAM_BOUND_MASK;
+ reg |= S5P_ENC_STREAM_INT_EN;
+ reg |= size & S5P_ENC_STREAM_BOUND_MASK;
+ writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE);
+}
+
+int s5p_jpeg_enc_stream_stat(void __iomem *regs)
+{
+ return (int)(readl(regs + S5P_JPG_ENC_STREAM_INTST) &
+ S5P_ENC_STREAM_INT_STAT_MASK);
+}
+
+void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_ENC_STREAM_INTSE);
+ reg &= ~S5P_ENC_STREAM_INT_MASK;
+ writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE);
+}
+
+void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format)
+{
+ unsigned long reg, f;
+
+ f = S5P_DEC_OUT_FORMAT_422;
+ if (format == S5P_JPEG_RAW_OUT_422)
+ f = S5P_DEC_OUT_FORMAT_422;
+ else if (format == S5P_JPEG_RAW_OUT_420)
+ f = S5P_DEC_OUT_FORMAT_420;
+ reg = readl(regs + S5P_JPG_OUTFORM);
+ reg &= ~S5P_DEC_OUT_FORMAT_MASK;
+ reg |= f;
+ writel(reg, regs + S5P_JPG_OUTFORM);
+}
+
+void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr)
+{
+ writel(addr, regs + S5P_JPG_JPGADR);
+}
+
+void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr)
+{
+ writel(addr, regs + S5P_JPG_IMGADR);
+}
+
+void s5p_jpeg_coef(void __iomem *regs, unsigned int i,
+ unsigned int j, unsigned int coef)
+{
+ unsigned long reg;
+
+ reg = readl(regs + S5P_JPG_COEF(i));
+ reg &= ~S5P_COEFn_MASK(j);
+ reg |= (coef << S5P_COEFn_SHIFT(j)) & S5P_COEFn_MASK(j);
+ writel(reg, regs + S5P_JPG_COEF(i));
+}
+
+void s5p_jpeg_start(void __iomem *regs)
+{
+ writel(1, regs + S5P_JSTART);
+}
+
+int s5p_jpeg_result_stat_ok(void __iomem *regs)
+{
+ return (int)((readl(regs + S5P_JPGINTST) & S5P_RESULT_STAT_MASK)
+ >> S5P_RESULT_STAT_SHIFT);
+}
+
+int s5p_jpeg_stream_stat_ok(void __iomem *regs)
+{
+ return !(int)((readl(regs + S5P_JPGINTST) & S5P_STREAM_STAT_MASK)
+ >> S5P_STREAM_STAT_SHIFT);
+}
+
+void s5p_jpeg_clear_int(void __iomem *regs)
+{
+ readl(regs + S5P_JPGINTST);
+ writel(S5P_INT_RELEASE, regs + S5P_JPGCOM);
+ readl(regs + S5P_JPGOPR);
+}
+
+unsigned int s5p_jpeg_compressed_size(void __iomem *regs)
+{
+ unsigned long jpeg_size = 0;
+
+ jpeg_size |= (readl(regs + S5P_JPGCNT_U) & 0xff) << 16;
+ jpeg_size |= (readl(regs + S5P_JPGCNT_M) & 0xff) << 8;
+ jpeg_size |= (readl(regs + S5P_JPGCNT_L) & 0xff);
+
+ return (unsigned int)jpeg_size;
+}
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h
new file mode 100644
index 0000000000..f068d52c66
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ */
+#ifndef JPEG_HW_S5P_H_
+#define JPEG_HW_S5P_H_
+
+#include <linux/io.h>
+#include <linux/videodev2.h>
+
+#include "jpeg-regs.h"
+
+#define S5P_JPEG_MIN_WIDTH 32
+#define S5P_JPEG_MIN_HEIGHT 32
+#define S5P_JPEG_MAX_WIDTH 8192
+#define S5P_JPEG_MAX_HEIGHT 8192
+#define S5P_JPEG_RAW_IN_565 0
+#define S5P_JPEG_RAW_IN_422 1
+#define S5P_JPEG_RAW_OUT_422 0
+#define S5P_JPEG_RAW_OUT_420 1
+
+void s5p_jpeg_reset(void __iomem *regs);
+void s5p_jpeg_poweron(void __iomem *regs);
+void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode);
+void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode);
+void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode);
+unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs);
+void s5p_jpeg_dri(void __iomem *regs, unsigned int dri);
+void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n);
+void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t);
+void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t);
+void s5p_jpeg_y(void __iomem *regs, unsigned int y);
+void s5p_jpeg_x(void __iomem *regs, unsigned int x);
+void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable);
+void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable);
+void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl);
+int s5p_jpeg_timer_stat(void __iomem *regs);
+void s5p_jpeg_clear_timer_stat(void __iomem *regs);
+void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size);
+int s5p_jpeg_enc_stream_stat(void __iomem *regs);
+void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs);
+void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format);
+void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr);
+void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr);
+void s5p_jpeg_coef(void __iomem *regs, unsigned int i,
+ unsigned int j, unsigned int coef);
+void s5p_jpeg_start(void __iomem *regs);
+int s5p_jpeg_result_stat_ok(void __iomem *regs);
+int s5p_jpeg_stream_stat_ok(void __iomem *regs);
+void s5p_jpeg_clear_int(void __iomem *regs);
+unsigned int s5p_jpeg_compressed_size(void __iomem *regs);
+
+#endif /* JPEG_HW_S5P_H_ */
diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h
new file mode 100644
index 0000000000..c2298b6800
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h
@@ -0,0 +1,646 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h
+ *
+ * Register definition file for Samsung JPEG codec driver
+ *
+ * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Author: Andrzej Pietrasiewicz <andrzejtp2010@gmail.com>
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+
+#ifndef JPEG_REGS_H_
+#define JPEG_REGS_H_
+
+/* Register and bit definitions for S5PC210 */
+
+/* JPEG mode register */
+#define S5P_JPGMOD 0x00
+#define S5P_PROC_MODE_MASK (0x1 << 3)
+#define S5P_PROC_MODE_DECOMPR (0x1 << 3)
+#define S5P_PROC_MODE_COMPR (0x0 << 3)
+#define S5P_SUBSAMPLING_MODE_MASK 0x7
+#define S5P_SUBSAMPLING_MODE_444 (0x0 << 0)
+#define S5P_SUBSAMPLING_MODE_422 (0x1 << 0)
+#define S5P_SUBSAMPLING_MODE_420 (0x2 << 0)
+#define S5P_SUBSAMPLING_MODE_GRAY (0x3 << 0)
+
+/* JPEG operation status register */
+#define S5P_JPGOPR 0x04
+
+/* Quantization tables*/
+#define S5P_JPG_QTBL 0x08
+#define S5P_QT_NUMt_SHIFT(t) (((t) - 1) << 1)
+#define S5P_QT_NUMt_MASK(t) (0x3 << S5P_QT_NUMt_SHIFT(t))
+
+/* Huffman tables */
+#define S5P_JPG_HTBL 0x0c
+#define S5P_HT_NUMt_AC_SHIFT(t) (((t) << 1) - 1)
+#define S5P_HT_NUMt_AC_MASK(t) (0x1 << S5P_HT_NUMt_AC_SHIFT(t))
+
+#define S5P_HT_NUMt_DC_SHIFT(t) (((t) - 1) << 1)
+#define S5P_HT_NUMt_DC_MASK(t) (0x1 << S5P_HT_NUMt_DC_SHIFT(t))
+
+/* JPEG restart interval register upper byte */
+#define S5P_JPGDRI_U 0x10
+
+/* JPEG restart interval register lower byte */
+#define S5P_JPGDRI_L 0x14
+
+/* JPEG vertical resolution register upper byte */
+#define S5P_JPGY_U 0x18
+
+/* JPEG vertical resolution register lower byte */
+#define S5P_JPGY_L 0x1c
+
+/* JPEG horizontal resolution register upper byte */
+#define S5P_JPGX_U 0x20
+
+/* JPEG horizontal resolution register lower byte */
+#define S5P_JPGX_L 0x24
+
+/* JPEG byte count register upper byte */
+#define S5P_JPGCNT_U 0x28
+
+/* JPEG byte count register middle byte */
+#define S5P_JPGCNT_M 0x2c
+
+/* JPEG byte count register lower byte */
+#define S5P_JPGCNT_L 0x30
+
+/* JPEG interrupt setting register */
+#define S5P_JPGINTSE 0x34
+#define S5P_RSTm_INT_EN_MASK (0x1 << 7)
+#define S5P_RSTm_INT_EN (0x1 << 7)
+#define S5P_DATA_NUM_INT_EN_MASK (0x1 << 6)
+#define S5P_DATA_NUM_INT_EN (0x1 << 6)
+#define S5P_FINAL_MCU_NUM_INT_EN_MASK (0x1 << 5)
+#define S5P_FINAL_MCU_NUM_INT_EN (0x1 << 5)
+
+/* JPEG interrupt status register */
+#define S5P_JPGINTST 0x38
+#define S5P_RESULT_STAT_SHIFT 6
+#define S5P_RESULT_STAT_MASK (0x1 << S5P_RESULT_STAT_SHIFT)
+#define S5P_STREAM_STAT_SHIFT 5
+#define S5P_STREAM_STAT_MASK (0x1 << S5P_STREAM_STAT_SHIFT)
+
+/* JPEG command register */
+#define S5P_JPGCOM 0x4c
+#define S5P_INT_RELEASE (0x1 << 2)
+
+/* Raw image data r/w address register */
+#define S5P_JPG_IMGADR 0x50
+
+/* JPEG file r/w address register */
+#define S5P_JPG_JPGADR 0x58
+
+/* Coefficient for RGB-to-YCbCr converter register */
+#define S5P_JPG_COEF(n) (0x5c + (((n) - 1) << 2))
+#define S5P_COEFn_SHIFT(j) ((3 - (j)) << 3)
+#define S5P_COEFn_MASK(j) (0xff << S5P_COEFn_SHIFT(j))
+
+/* JPEG color mode register */
+#define S5P_JPGCMOD 0x68
+#define S5P_MOD_SEL_MASK (0x7 << 5)
+#define S5P_MOD_SEL_422 (0x1 << 5)
+#define S5P_MOD_SEL_565 (0x2 << 5)
+#define S5P_MODE_Y16_MASK (0x1 << 1)
+#define S5P_MODE_Y16 (0x1 << 1)
+
+/* JPEG clock control register */
+#define S5P_JPGCLKCON 0x6c
+#define S5P_CLK_DOWN_READY (0x1 << 1)
+#define S5P_POWER_ON (0x1 << 0)
+
+/* JPEG start register */
+#define S5P_JSTART 0x70
+
+/* JPEG SW reset register */
+#define S5P_JPG_SW_RESET 0x78
+
+/* JPEG timer setting register */
+#define S5P_JPG_TIMER_SE 0x7c
+#define S5P_TIMER_INT_EN_MASK (0x1UL << 31)
+#define S5P_TIMER_INT_EN (0x1UL << 31)
+#define S5P_TIMER_INIT_MASK 0x7fffffff
+
+/* JPEG timer status register */
+#define S5P_JPG_TIMER_ST 0x80
+#define S5P_TIMER_INT_STAT_SHIFT 31
+#define S5P_TIMER_INT_STAT_MASK (0x1UL << S5P_TIMER_INT_STAT_SHIFT)
+#define S5P_TIMER_CNT_SHIFT 0
+#define S5P_TIMER_CNT_MASK 0x7fffffff
+
+/* JPEG decompression output format register */
+#define S5P_JPG_OUTFORM 0x88
+#define S5P_DEC_OUT_FORMAT_MASK (0x1 << 0)
+#define S5P_DEC_OUT_FORMAT_422 (0x0 << 0)
+#define S5P_DEC_OUT_FORMAT_420 (0x1 << 0)
+
+/* JPEG version register */
+#define S5P_JPG_VERSION 0x8c
+
+/* JPEG compressed stream size interrupt setting register */
+#define S5P_JPG_ENC_STREAM_INTSE 0x98
+#define S5P_ENC_STREAM_INT_MASK (0x1 << 24)
+#define S5P_ENC_STREAM_INT_EN (0x1 << 24)
+#define S5P_ENC_STREAM_BOUND_MASK 0xffffff
+
+/* JPEG compressed stream size interrupt status register */
+#define S5P_JPG_ENC_STREAM_INTST 0x9c
+#define S5P_ENC_STREAM_INT_STAT_MASK 0x1
+
+/* JPEG quantizer table register */
+#define S5P_JPG_QTBL_CONTENT(n) (0x400 + (n) * 0x100)
+
+/* JPEG DC Huffman table register */
+#define S5P_JPG_HDCTBL(n) (0x800 + (n) * 0x400)
+
+/* JPEG DC Huffman table register */
+#define S5P_JPG_HDCTBLG(n) (0x840 + (n) * 0x400)
+
+/* JPEG AC Huffman table register */
+#define S5P_JPG_HACTBL(n) (0x880 + (n) * 0x400)
+
+/* JPEG AC Huffman table register */
+#define S5P_JPG_HACTBLG(n) (0x8c0 + (n) * 0x400)
+
+
+/* Register and bit definitions for Exynos 4x12 */
+
+/* JPEG Codec Control Registers */
+#define EXYNOS4_JPEG_CNTL_REG 0x00
+#define EXYNOS4_INT_EN_REG 0x04
+#define EXYNOS4_INT_TIMER_COUNT_REG 0x08
+#define EXYNOS4_INT_STATUS_REG 0x0c
+#define EXYNOS4_OUT_MEM_BASE_REG 0x10
+#define EXYNOS4_JPEG_IMG_SIZE_REG 0x14
+#define EXYNOS4_IMG_BA_PLANE_1_REG 0x18
+#define EXYNOS4_IMG_SO_PLANE_1_REG 0x1c
+#define EXYNOS4_IMG_PO_PLANE_1_REG 0x20
+#define EXYNOS4_IMG_BA_PLANE_2_REG 0x24
+#define EXYNOS4_IMG_SO_PLANE_2_REG 0x28
+#define EXYNOS4_IMG_PO_PLANE_2_REG 0x2c
+#define EXYNOS4_IMG_BA_PLANE_3_REG 0x30
+#define EXYNOS4_IMG_SO_PLANE_3_REG 0x34
+#define EXYNOS4_IMG_PO_PLANE_3_REG 0x38
+
+#define EXYNOS4_TBL_SEL_REG 0x3c
+
+#define EXYNOS4_IMG_FMT_REG 0x40
+
+#define EXYNOS4_BITSTREAM_SIZE_REG 0x44
+#define EXYNOS4_PADDING_REG 0x48
+#define EXYNOS4_HUFF_CNT_REG 0x4c
+#define EXYNOS4_FIFO_STATUS_REG 0x50
+#define EXYNOS4_DECODE_XY_SIZE_REG 0x54
+#define EXYNOS4_DECODE_IMG_FMT_REG 0x58
+
+#define EXYNOS4_QUAN_TBL_ENTRY_REG 0x100
+#define EXYNOS4_HUFF_TBL_ENTRY_REG 0x200
+
+
+/****************************************************************/
+/* Bit definition part */
+/****************************************************************/
+
+/* JPEG CNTL Register bit */
+#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0)
+#define EXYNOS4_DEC_MODE (1 << 0)
+#define EXYNOS4_ENC_MODE (1 << 1)
+#define EXYNOS4_AUTO_RST_MARKER (1 << 2)
+#define EXYNOS4_RST_INTERVAL_SHIFT 3
+#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \
+ << EXYNOS4_RST_INTERVAL_SHIFT)
+#define EXYNOS4_HUF_TBL_EN (1 << 19)
+#define EXYNOS4_HOR_SCALING_SHIFT 20
+#define EXYNOS4_HOR_SCALING_MASK (3 << EXYNOS4_HOR_SCALING_SHIFT)
+#define EXYNOS4_HOR_SCALING(x) (((x) & 0x3) \
+ << EXYNOS4_HOR_SCALING_SHIFT)
+#define EXYNOS4_VER_SCALING_SHIFT 22
+#define EXYNOS4_VER_SCALING_MASK (3 << EXYNOS4_VER_SCALING_SHIFT)
+#define EXYNOS4_VER_SCALING(x) (((x) & 0x3) \
+ << EXYNOS4_VER_SCALING_SHIFT)
+#define EXYNOS4_PADDING (1 << 27)
+#define EXYNOS4_SYS_INT_EN (1 << 28)
+#define EXYNOS4_SOFT_RESET_HI (1 << 29)
+
+/* JPEG INT Register bit */
+#define EXYNOS4_INT_EN_MASK (0x1f << 0)
+#define EXYNOS5433_INT_EN_MASK (0x1ff << 0)
+#define EXYNOS4_PROT_ERR_INT_EN (1 << 0)
+#define EXYNOS4_IMG_COMPLETION_INT_EN (1 << 1)
+#define EXYNOS4_DEC_INVALID_FORMAT_EN (1 << 2)
+#define EXYNOS4_MULTI_SCAN_ERROR_EN (1 << 3)
+#define EXYNOS4_FRAME_ERR_EN (1 << 4)
+#define EXYNOS4_INT_EN_ALL (0x1f << 0)
+#define EXYNOS5433_INT_EN_ALL (0x1b6 << 0)
+
+#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3)
+#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3)
+
+#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0)
+#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0)
+#define EXYNOS4_MOD_REG_SUBSAMPLE_420 (2 << 0)
+#define EXYNOS4_MOD_REG_SUBSAMPLE_GRAY (3 << 0)
+
+
+/* JPEG IMAGE SIZE Register bit */
+#define EXYNOS4_X_SIZE_SHIFT 0
+#define EXYNOS4_X_SIZE_MASK (0xffff << EXYNOS4_X_SIZE_SHIFT)
+#define EXYNOS4_X_SIZE(x) (((x) & 0xffff) << EXYNOS4_X_SIZE_SHIFT)
+#define EXYNOS4_Y_SIZE_SHIFT 16
+#define EXYNOS4_Y_SIZE_MASK (0xffff << EXYNOS4_Y_SIZE_SHIFT)
+#define EXYNOS4_Y_SIZE(x) (((x) & 0xffff) << EXYNOS4_Y_SIZE_SHIFT)
+
+/* JPEG IMAGE FORMAT Register bit */
+#define EXYNOS4_ENC_IN_FMT_MASK 0xffff0000
+#define EXYNOS4_ENC_GRAY_IMG (0 << 0)
+#define EXYNOS4_ENC_RGB_IMG (1 << 0)
+#define EXYNOS4_ENC_YUV_444_IMG (2 << 0)
+#define EXYNOS4_ENC_YUV_422_IMG (3 << 0)
+#define EXYNOS4_ENC_YUV_440_IMG (4 << 0)
+
+#define EXYNOS4_DEC_GRAY_IMG (0 << 0)
+#define EXYNOS4_DEC_RGB_IMG (1 << 0)
+#define EXYNOS4_DEC_YUV_444_IMG (2 << 0)
+#define EXYNOS4_DEC_YUV_422_IMG (3 << 0)
+#define EXYNOS4_DEC_YUV_420_IMG (4 << 0)
+
+#define EXYNOS4_GRAY_IMG_IP_SHIFT 3
+#define EXYNOS4_GRAY_IMG_IP_MASK (7 << EXYNOS4_GRAY_IMG_IP_SHIFT)
+#define EXYNOS4_GRAY_IMG_IP (4 << EXYNOS4_GRAY_IMG_IP_SHIFT)
+
+#define EXYNOS4_RGB_IP_SHIFT 6
+#define EXYNOS4_RGB_IP_MASK (7 << EXYNOS4_RGB_IP_SHIFT)
+#define EXYNOS4_RGB_IP_RGB_16BIT_IMG (4 << EXYNOS4_RGB_IP_SHIFT)
+#define EXYNOS4_RGB_IP_RGB_32BIT_IMG (5 << EXYNOS4_RGB_IP_SHIFT)
+
+#define EXYNOS4_YUV_444_IP_SHIFT 9
+#define EXYNOS4_YUV_444_IP_MASK (7 << EXYNOS4_YUV_444_IP_SHIFT)
+#define EXYNOS4_YUV_444_IP_YUV_444_2P_IMG (4 << EXYNOS4_YUV_444_IP_SHIFT)
+#define EXYNOS4_YUV_444_IP_YUV_444_3P_IMG (5 << EXYNOS4_YUV_444_IP_SHIFT)
+
+#define EXYNOS4_YUV_422_IP_SHIFT 12
+#define EXYNOS4_YUV_422_IP_MASK (7 << EXYNOS4_YUV_422_IP_SHIFT)
+#define EXYNOS4_YUV_422_IP_YUV_422_1P_IMG (4 << EXYNOS4_YUV_422_IP_SHIFT)
+#define EXYNOS4_YUV_422_IP_YUV_422_2P_IMG (5 << EXYNOS4_YUV_422_IP_SHIFT)
+#define EXYNOS4_YUV_422_IP_YUV_422_3P_IMG (6 << EXYNOS4_YUV_422_IP_SHIFT)
+
+#define EXYNOS4_YUV_420_IP_SHIFT 15
+#define EXYNOS4_YUV_420_IP_MASK (7 << EXYNOS4_YUV_420_IP_SHIFT)
+#define EXYNOS4_YUV_420_IP_YUV_420_2P_IMG (4 << EXYNOS4_YUV_420_IP_SHIFT)
+#define EXYNOS4_YUV_420_IP_YUV_420_3P_IMG (5 << EXYNOS4_YUV_420_IP_SHIFT)
+
+#define EXYNOS4_ENC_FMT_SHIFT 24
+#define EXYNOS4_ENC_FMT_MASK (3 << EXYNOS4_ENC_FMT_SHIFT)
+#define EXYNOS5433_ENC_FMT_MASK (7 << EXYNOS4_ENC_FMT_SHIFT)
+
+#define EXYNOS4_ENC_FMT_GRAY (0 << EXYNOS4_ENC_FMT_SHIFT)
+#define EXYNOS4_ENC_FMT_YUV_444 (1 << EXYNOS4_ENC_FMT_SHIFT)
+#define EXYNOS4_ENC_FMT_YUV_422 (2 << EXYNOS4_ENC_FMT_SHIFT)
+#define EXYNOS4_ENC_FMT_YUV_420 (3 << EXYNOS4_ENC_FMT_SHIFT)
+
+#define EXYNOS4_JPEG_DECODED_IMG_FMT_MASK 0x03
+
+#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26)
+#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26)
+#define EXYNOS5433_SWAP_CHROMA_CRCB (1 << 27)
+#define EXYNOS5433_SWAP_CHROMA_CBCR (0 << 27)
+
+/* JPEG HUFF count Register bit */
+#define EXYNOS4_HUFF_COUNT_MASK 0xffff
+
+/* JPEG Decoded_img_x_y_size Register bit */
+#define EXYNOS4_DECODED_SIZE_MASK 0x0000ffff
+
+/* JPEG Decoded image format Register bit */
+#define EXYNOS4_DECODED_IMG_FMT_MASK 0x3
+
+/* JPEG TBL SEL Register bit */
+#define EXYNOS4_Q_TBL_COMP(c, n) ((n) << (((c) - 1) << 1))
+
+#define EXYNOS4_Q_TBL_COMP1_0 EXYNOS4_Q_TBL_COMP(1, 0)
+#define EXYNOS4_Q_TBL_COMP1_1 EXYNOS4_Q_TBL_COMP(1, 1)
+#define EXYNOS4_Q_TBL_COMP1_2 EXYNOS4_Q_TBL_COMP(1, 2)
+#define EXYNOS4_Q_TBL_COMP1_3 EXYNOS4_Q_TBL_COMP(1, 3)
+
+#define EXYNOS4_Q_TBL_COMP2_0 EXYNOS4_Q_TBL_COMP(2, 0)
+#define EXYNOS4_Q_TBL_COMP2_1 EXYNOS4_Q_TBL_COMP(2, 1)
+#define EXYNOS4_Q_TBL_COMP2_2 EXYNOS4_Q_TBL_COMP(2, 2)
+#define EXYNOS4_Q_TBL_COMP2_3 EXYNOS4_Q_TBL_COMP(2, 3)
+
+#define EXYNOS4_Q_TBL_COMP3_0 EXYNOS4_Q_TBL_COMP(3, 0)
+#define EXYNOS4_Q_TBL_COMP3_1 EXYNOS4_Q_TBL_COMP(3, 1)
+#define EXYNOS4_Q_TBL_COMP3_2 EXYNOS4_Q_TBL_COMP(3, 2)
+#define EXYNOS4_Q_TBL_COMP3_3 EXYNOS4_Q_TBL_COMP(3, 3)
+
+#define EXYNOS4_HUFF_TBL_COMP(c, n) ((n) << ((((c) - 1) << 1) + 6))
+
+#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(1, 0)
+#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(1, 1)
+#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(1, 2)
+#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(1, 3)
+
+#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(2, 0)
+#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(2, 1)
+#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(2, 2)
+#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(2, 3)
+
+#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(3, 0)
+#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(3, 1)
+#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_0 \
+ EXYNOS4_HUFF_TBL_COMP(3, 2)
+#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1 \
+ EXYNOS4_HUFF_TBL_COMP(3, 3)
+
+#define EXYNOS4_NF_SHIFT 16
+#define EXYNOS4_NF_MASK 0xff
+#define EXYNOS4_NF(x) \
+ (((x) & EXYNOS4_NF_MASK) << EXYNOS4_NF_SHIFT)
+
+/* JPEG quantizer table register */
+#define EXYNOS4_QTBL_CONTENT(n) (0x100 + (n) * 0x40)
+
+/* JPEG DC luminance (code length) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HDCLL 0x200
+
+/* JPEG DC luminance (values) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HDCLV 0x210
+
+/* JPEG DC chrominance (code length) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HDCCL 0x220
+
+/* JPEG DC chrominance (values) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HDCCV 0x230
+
+/* JPEG AC luminance (code length) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HACLL 0x240
+
+/* JPEG AC luminance (values) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HACLV 0x250
+
+/* JPEG AC chrominance (code length) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HACCL 0x300
+
+/* JPEG AC chrominance (values) Huffman table register */
+#define EXYNOS4_HUFF_TBL_HACCV 0x310
+
+/* Register and bit definitions for Exynos 3250 */
+
+/* JPEG mode register */
+#define EXYNOS3250_JPGMOD 0x00
+#define EXYNOS3250_PROC_MODE_MASK (0x1 << 3)
+#define EXYNOS3250_PROC_MODE_DECOMPR (0x1 << 3)
+#define EXYNOS3250_PROC_MODE_COMPR (0x0 << 3)
+#define EXYNOS3250_SUBSAMPLING_MODE_MASK (0x7 << 0)
+#define EXYNOS3250_SUBSAMPLING_MODE_444 (0x0 << 0)
+#define EXYNOS3250_SUBSAMPLING_MODE_422 (0x1 << 0)
+#define EXYNOS3250_SUBSAMPLING_MODE_420 (0x2 << 0)
+#define EXYNOS3250_SUBSAMPLING_MODE_411 (0x6 << 0)
+#define EXYNOS3250_SUBSAMPLING_MODE_GRAY (0x3 << 0)
+
+/* JPEG operation status register */
+#define EXYNOS3250_JPGOPR 0x04
+#define EXYNOS3250_JPGOPR_MASK 0x01
+
+/* Quantization and Huffman tables register */
+#define EXYNOS3250_QHTBL 0x08
+#define EXYNOS3250_QT_NUM_SHIFT(t) ((((t) - 1) << 1) + 8)
+#define EXYNOS3250_QT_NUM_MASK(t) (0x3 << EXYNOS3250_QT_NUM_SHIFT(t))
+
+/* Huffman tables */
+#define EXYNOS3250_HT_NUM_AC_SHIFT(t) (((t) << 1) - 1)
+#define EXYNOS3250_HT_NUM_AC_MASK(t) (0x1 << EXYNOS3250_HT_NUM_AC_SHIFT(t))
+
+#define EXYNOS3250_HT_NUM_DC_SHIFT(t) (((t) - 1) << 1)
+#define EXYNOS3250_HT_NUM_DC_MASK(t) (0x1 << EXYNOS3250_HT_NUM_DC_SHIFT(t))
+
+/* JPEG restart interval register */
+#define EXYNOS3250_JPGDRI 0x0c
+#define EXYNOS3250_JPGDRI_MASK 0xffff
+
+/* JPEG vertical resolution register */
+#define EXYNOS3250_JPGY 0x10
+#define EXYNOS3250_JPGY_MASK 0xffff
+
+/* JPEG horizontal resolution register */
+#define EXYNOS3250_JPGX 0x14
+#define EXYNOS3250_JPGX_MASK 0xffff
+
+/* JPEG byte count register */
+#define EXYNOS3250_JPGCNT 0x18
+#define EXYNOS3250_JPGCNT_MASK 0xffffff
+
+/* JPEG interrupt mask register */
+#define EXYNOS3250_JPGINTSE 0x1c
+#define EXYNOS3250_JPEG_DONE_EN (1 << 11)
+#define EXYNOS3250_WDMA_DONE_EN (1 << 10)
+#define EXYNOS3250_RDMA_DONE_EN (1 << 9)
+#define EXYNOS3250_ENC_STREAM_INT_EN (1 << 8)
+#define EXYNOS3250_CORE_DONE_EN (1 << 5)
+#define EXYNOS3250_ERR_INT_EN (1 << 4)
+#define EXYNOS3250_HEAD_INT_EN (1 << 3)
+
+/* JPEG interrupt status register */
+#define EXYNOS3250_JPGINTST 0x20
+#define EXYNOS3250_JPEG_DONE (1 << 11)
+#define EXYNOS3250_WDMA_DONE (1 << 10)
+#define EXYNOS3250_RDMA_DONE (1 << 9)
+#define EXYNOS3250_ENC_STREAM_STAT (1 << 8)
+#define EXYNOS3250_RESULT_STAT (1 << 5)
+#define EXYNOS3250_STREAM_STAT (1 << 4)
+#define EXYNOS3250_HEADER_STAT (1 << 3)
+
+/*
+ * Base address of the luma component DMA buffer
+ * of the raw input or output image.
+ */
+#define EXYNOS3250_LUMA_BASE 0x100
+#define EXYNOS3250_SRC_TILE_EN_MASK 0x100
+
+/* Stride of source or destination luma raw image buffer */
+#define EXYNOS3250_LUMA_STRIDE 0x104
+
+/* Horizontal/vertical offset of active region in luma raw image buffer */
+#define EXYNOS3250_LUMA_XY_OFFSET 0x108
+#define EXYNOS3250_LUMA_YY_OFFSET_SHIFT 18
+#define EXYNOS3250_LUMA_YY_OFFSET_MASK (0x1fff << EXYNOS3250_LUMA_YY_OFFSET_SHIFT)
+#define EXYNOS3250_LUMA_YX_OFFSET_SHIFT 2
+#define EXYNOS3250_LUMA_YX_OFFSET_MASK (0x1fff << EXYNOS3250_LUMA_YX_OFFSET_SHIFT)
+
+/*
+ * Base address of the chroma(Cb) component DMA buffer
+ * of the raw input or output image.
+ */
+#define EXYNOS3250_CHROMA_BASE 0x10c
+
+/* Stride of source or destination chroma(Cb) raw image buffer */
+#define EXYNOS3250_CHROMA_STRIDE 0x110
+
+/* Horizontal/vertical offset of active region in chroma(Cb) raw image buffer */
+#define EXYNOS3250_CHROMA_XY_OFFSET 0x114
+#define EXYNOS3250_CHROMA_YY_OFFSET_SHIFT 18
+#define EXYNOS3250_CHROMA_YY_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_YY_OFFSET_SHIFT)
+#define EXYNOS3250_CHROMA_YX_OFFSET_SHIFT 2
+#define EXYNOS3250_CHROMA_YX_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_YX_OFFSET_SHIFT)
+
+/*
+ * Base address of the chroma(Cr) component DMA buffer
+ * of the raw input or output image.
+ */
+#define EXYNOS3250_CHROMA_CR_BASE 0x118
+
+/* Stride of source or destination chroma(Cr) raw image buffer */
+#define EXYNOS3250_CHROMA_CR_STRIDE 0x11c
+
+/* Horizontal/vertical offset of active region in chroma(Cb) raw image buffer */
+#define EXYNOS3250_CHROMA_CR_XY_OFFSET 0x120
+#define EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT 18
+#define EXYNOS3250_CHROMA_CR_YY_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT)
+#define EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT 2
+#define EXYNOS3250_CHROMA_CR_YX_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT)
+
+/* Raw image data r/w address register */
+#define EXYNOS3250_JPG_IMGADR 0x50
+
+/* Source or destination JPEG file DMA buffer address */
+#define EXYNOS3250_JPG_JPGADR 0x124
+
+/* Coefficients for RGB-to-YCbCr converter register */
+#define EXYNOS3250_JPG_COEF(n) (0x128 + (((n) - 1) << 2))
+#define EXYNOS3250_COEF_SHIFT(j) ((3 - (j)) << 3)
+#define EXYNOS3250_COEF_MASK(j) (0xff << EXYNOS3250_COEF_SHIFT(j))
+
+/* Raw input format setting */
+#define EXYNOS3250_JPGCMOD 0x134
+#define EXYNOS3250_SRC_TILE_EN (0x1 << 10)
+#define EXYNOS3250_SRC_NV_MASK (0x1 << 9)
+#define EXYNOS3250_SRC_NV12 (0x0 << 9)
+#define EXYNOS3250_SRC_NV21 (0x1 << 9)
+#define EXYNOS3250_SRC_BIG_ENDIAN_MASK (0x1 << 8)
+#define EXYNOS3250_SRC_BIG_ENDIAN (0x1 << 8)
+#define EXYNOS3250_MODE_SEL_MASK (0x7 << 5)
+#define EXYNOS3250_MODE_SEL_420_2P (0x0 << 5)
+#define EXYNOS3250_MODE_SEL_422_1P_LUM_CHR (0x1 << 5)
+#define EXYNOS3250_MODE_SEL_RGB565 (0x2 << 5)
+#define EXYNOS3250_MODE_SEL_422_1P_CHR_LUM (0x3 << 5)
+#define EXYNOS3250_MODE_SEL_ARGB8888 (0x4 << 5)
+#define EXYNOS3250_MODE_SEL_420_3P (0x5 << 5)
+#define EXYNOS3250_SRC_SWAP_RGB (0x1 << 3)
+#define EXYNOS3250_SRC_SWAP_UV (0x1 << 2)
+#define EXYNOS3250_MODE_Y16_MASK (0x1 << 1)
+#define EXYNOS3250_MODE_Y16 (0x1 << 1)
+#define EXYNOS3250_HALF_EN_MASK (0x1 << 0)
+#define EXYNOS3250_HALF_EN (0x1 << 0)
+
+/* Power on/off and clock down control */
+#define EXYNOS3250_JPGCLKCON 0x138
+#define EXYNOS3250_CLK_DOWN_READY (0x1 << 1)
+#define EXYNOS3250_POWER_ON (0x1 << 0)
+
+/* Start compression or decompression */
+#define EXYNOS3250_JSTART 0x13c
+
+/* Restart decompression after header analysis */
+#define EXYNOS3250_JRSTART 0x140
+
+/* JPEG SW reset register */
+#define EXYNOS3250_SW_RESET 0x144
+
+/* JPEG timer setting register */
+#define EXYNOS3250_TIMER_SE 0x148
+#define EXYNOS3250_TIMER_INT_EN_SHIFT 31
+#define EXYNOS3250_TIMER_INT_EN (1UL << EXYNOS3250_TIMER_INT_EN_SHIFT)
+#define EXYNOS3250_TIMER_INIT_MASK 0x7fffffff
+
+/* JPEG timer status register */
+#define EXYNOS3250_TIMER_ST 0x14c
+#define EXYNOS3250_TIMER_INT_STAT_SHIFT 31
+#define EXYNOS3250_TIMER_INT_STAT (1UL << EXYNOS3250_TIMER_INT_STAT_SHIFT)
+#define EXYNOS3250_TIMER_CNT_SHIFT 0
+#define EXYNOS3250_TIMER_CNT_MASK 0x7fffffff
+
+/* Command status register */
+#define EXYNOS3250_COMSTAT 0x150
+#define EXYNOS3250_CUR_PROC_MODE (0x1 << 1)
+#define EXYNOS3250_CUR_COM_MODE (0x1 << 0)
+
+/* JPEG decompression output format register */
+#define EXYNOS3250_OUTFORM 0x154
+#define EXYNOS3250_OUT_ALPHA_MASK (0xff << 24)
+#define EXYNOS3250_OUT_TILE_EN (0x1 << 10)
+#define EXYNOS3250_OUT_NV_MASK (0x1 << 9)
+#define EXYNOS3250_OUT_NV12 (0x0 << 9)
+#define EXYNOS3250_OUT_NV21 (0x1 << 9)
+#define EXYNOS3250_OUT_BIG_ENDIAN_MASK (0x1 << 8)
+#define EXYNOS3250_OUT_BIG_ENDIAN (0x1 << 8)
+#define EXYNOS3250_OUT_SWAP_RGB (0x1 << 7)
+#define EXYNOS3250_OUT_SWAP_UV (0x1 << 6)
+#define EXYNOS3250_OUT_FMT_MASK (0x7 << 0)
+#define EXYNOS3250_OUT_FMT_420_2P (0x0 << 0)
+#define EXYNOS3250_OUT_FMT_422_1P_LUM_CHR (0x1 << 0)
+#define EXYNOS3250_OUT_FMT_422_1P_CHR_LUM (0x3 << 0)
+#define EXYNOS3250_OUT_FMT_420_3P (0x4 << 0)
+#define EXYNOS3250_OUT_FMT_RGB565 (0x5 << 0)
+#define EXYNOS3250_OUT_FMT_ARGB8888 (0x6 << 0)
+
+/* Input JPEG stream byte size for decompression */
+#define EXYNOS3250_DEC_STREAM_SIZE 0x158
+#define EXYNOS3250_DEC_STREAM_MASK 0x1fffffff
+
+/* The upper bound of the byte size of output compressed stream */
+#define EXYNOS3250_ENC_STREAM_BOUND 0x15c
+#define EXYNOS3250_ENC_STREAM_BOUND_MASK 0xffffc0
+
+/* Scale-down ratio when decoding */
+#define EXYNOS3250_DEC_SCALING_RATIO 0x160
+#define EXYNOS3250_DEC_SCALE_FACTOR_MASK 0x3
+#define EXYNOS3250_DEC_SCALE_FACTOR_8_8 0x0
+#define EXYNOS3250_DEC_SCALE_FACTOR_4_8 0x1
+#define EXYNOS3250_DEC_SCALE_FACTOR_2_8 0x2
+#define EXYNOS3250_DEC_SCALE_FACTOR_1_8 0x3
+
+/* Error check */
+#define EXYNOS3250_CRC_RESULT 0x164
+
+/* RDMA and WDMA operation status register */
+#define EXYNOS3250_DMA_OPER_STATUS 0x168
+#define EXYNOS3250_WDMA_OPER_STATUS (0x1 << 1)
+#define EXYNOS3250_RDMA_OPER_STATUS (0x1 << 0)
+
+/* DMA issue gathering number and issue number settings */
+#define EXYNOS3250_DMA_ISSUE_NUM 0x16c
+#define EXYNOS3250_WDMA_ISSUE_NUM_SHIFT 16
+#define EXYNOS3250_WDMA_ISSUE_NUM_MASK (0x7 << EXYNOS3250_WDMA_ISSUE_NUM_SHIFT)
+#define EXYNOS3250_RDMA_ISSUE_NUM_SHIFT 8
+#define EXYNOS3250_RDMA_ISSUE_NUM_MASK (0x7 << EXYNOS3250_RDMA_ISSUE_NUM_SHIFT)
+#define EXYNOS3250_ISSUE_GATHER_NUM_SHIFT 0
+#define EXYNOS3250_ISSUE_GATHER_NUM_MASK (0x7 << EXYNOS3250_ISSUE_GATHER_NUM_SHIFT)
+#define EXYNOS3250_DMA_MO_COUNT 0x7
+
+/* Version register */
+#define EXYNOS3250_VERSION 0x1fc
+
+/* RGB <-> YUV conversion coefficients */
+#define EXYNOS3250_JPEG_ENC_COEF1 0x01352e1e
+#define EXYNOS3250_JPEG_ENC_COEF2 0x00b0ae83
+#define EXYNOS3250_JPEG_ENC_COEF3 0x020cdc13
+
+#define EXYNOS3250_JPEG_DEC_COEF1 0x04a80199
+#define EXYNOS3250_JPEG_DEC_COEF2 0x04a9a064
+#define EXYNOS3250_JPEG_DEC_COEF3 0x04a80102
+
+#endif /* JPEG_REGS_H_ */
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/Kconfig b/drivers/media/platform/samsung/s5p-mfc/Kconfig
new file mode 100644
index 0000000000..7ee3b0c8d9
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/Kconfig
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_SAMSUNG_S5P_MFC
+ tristate "Samsung S5P MFC Video Codec"
+ depends on V4L_MEM2MEM_DRIVERS
+ depends on VIDEO_DEV
+ depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ select VIDEOBUF2_DMA_CONTIG
+ help
+ MFC 5.1 and 6.x driver for V4L2
diff --git a/drivers/media/platform/samsung/s5p-mfc/Makefile b/drivers/media/platform/samsung/s5p-mfc/Makefile
new file mode 100644
index 0000000000..0b324af2ab
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MFC) += s5p-mfc.o
+s5p-mfc-y += s5p_mfc.o s5p_mfc_intr.o
+s5p-mfc-y += s5p_mfc_dec.o s5p_mfc_enc.o
+s5p-mfc-y += s5p_mfc_ctrl.o s5p_mfc_pm.o
+s5p-mfc-y += s5p_mfc_opr.o s5p_mfc_opr_v5.o s5p_mfc_opr_v6.o
+s5p-mfc-y += s5p_mfc_cmd.o s5p_mfc_cmd_v5.o s5p_mfc_cmd_v6.o
diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h
new file mode 100644
index 0000000000..fadd9139b4
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Register definition file for Samsung MFC V10.x Interface (FIMV) driver
+ *
+ */
+
+#ifndef _REGS_MFC_V10_H
+#define _REGS_MFC_V10_H
+
+#include <linux/sizes.h>
+#include "regs-mfc-v8.h"
+
+/* MFCv10 register definitions*/
+#define S5P_FIMV_MFC_CLOCK_OFF_V10 0x7120
+#define S5P_FIMV_MFC_STATE_V10 0x7124
+#define S5P_FIMV_D_STATIC_BUFFER_ADDR_V10 0xF570
+#define S5P_FIMV_D_STATIC_BUFFER_SIZE_V10 0xF574
+#define S5P_FIMV_E_NUM_T_LAYER_V10 0xFBAC
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER0_V10 0xFBB0
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER1_V10 0xFBB4
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER2_V10 0xFBB8
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER3_V10 0xFBBC
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER4_V10 0xFBC0
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER5_V10 0xFBC4
+#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER6_V10 0xFBC8
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER0_V10 0xFD18
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER1_V10 0xFD1C
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER2_V10 0xFD20
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER3_V10 0xFD24
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER4_V10 0xFD28
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER5_V10 0xFD2C
+#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER6_V10 0xFD30
+#define S5P_FIMV_E_HEVC_OPTIONS_V10 0xFDD4
+#define S5P_FIMV_E_HEVC_REFRESH_PERIOD_V10 0xFDD8
+#define S5P_FIMV_E_HEVC_CHROMA_QP_OFFSET_V10 0xFDDC
+#define S5P_FIMV_E_HEVC_LF_BETA_OFFSET_DIV2_V10 0xFDE0
+#define S5P_FIMV_E_HEVC_LF_TC_OFFSET_DIV2_V10 0xFDE4
+#define S5P_FIMV_E_HEVC_NAL_CONTROL_V10 0xFDE8
+
+/* MFCv10 Context buffer sizes */
+#define MFC_CTX_BUF_SIZE_V10 (30 * SZ_1K)
+#define MFC_H264_DEC_CTX_BUF_SIZE_V10 (2 * SZ_1M)
+#define MFC_OTHER_DEC_CTX_BUF_SIZE_V10 (20 * SZ_1K)
+#define MFC_H264_ENC_CTX_BUF_SIZE_V10 (100 * SZ_1K)
+#define MFC_HEVC_ENC_CTX_BUF_SIZE_V10 (30 * SZ_1K)
+#define MFC_OTHER_ENC_CTX_BUF_SIZE_V10 (15 * SZ_1K)
+
+/* MFCv10 variant defines */
+#define MAX_FW_SIZE_V10 (SZ_1M)
+#define MAX_CPB_SIZE_V10 (3 * SZ_1M)
+#define MFC_VERSION_V10 0xA0
+#define MFC_NUM_PORTS_V10 1
+
+/* MFCv10 codec defines*/
+#define S5P_FIMV_CODEC_HEVC_DEC 17
+#define S5P_FIMV_CODEC_VP9_DEC 18
+#define S5P_FIMV_CODEC_HEVC_ENC 26
+
+/* Decoder buffer size for MFC v10 */
+#define DEC_VP9_STATIC_BUFFER_SIZE 20480
+
+/* Encoder buffer size for MFC v10.0 */
+#define ENC_V100_BASE_SIZE(x, y) \
+ (((x + 3) * (y + 3) * 8) \
+ + ((y * 64) + 1280) * DIV_ROUND_UP(x, 8))
+
+#define ENC_V100_H264_ME_SIZE(x, y) \
+ (ENC_V100_BASE_SIZE(x, y) \
+ + (DIV_ROUND_UP(x * y, 64) * 32))
+
+#define ENC_V100_MPEG4_ME_SIZE(x, y) \
+ (ENC_V100_BASE_SIZE(x, y) \
+ + (DIV_ROUND_UP(x * y, 128) * 16))
+
+#define ENC_V100_VP8_ME_SIZE(x, y) \
+ ENC_V100_BASE_SIZE(x, y)
+
+#define ENC_V100_HEVC_ME_SIZE(x, y) \
+ (((x + 3) * (y + 3) * 32) \
+ + ((y * 128) + 1280) * DIV_ROUND_UP(x, 4))
+
+#endif /*_REGS_MFC_V10_H*/
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h
new file mode 100644
index 0000000000..fa49fe580e
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h
@@ -0,0 +1,408 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register definition file for Samsung MFC V6.x Interface (FIMV) driver
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef _REGS_FIMV_V6_H
+#define _REGS_FIMV_V6_H
+
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+
+#define S5P_FIMV_REG_SIZE_V6 (S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR)
+#define S5P_FIMV_REG_COUNT_V6 ((S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) / 4)
+
+/* Number of bits that the buffer address should be shifted for particular
+ * MFC buffers. */
+#define S5P_FIMV_MEM_OFFSET_V6 0
+
+#define S5P_FIMV_START_ADDR_V6 0x0000
+#define S5P_FIMV_END_ADDR_V6 0xfd80
+
+#define S5P_FIMV_REG_CLEAR_BEGIN_V6 0xf000
+#define S5P_FIMV_REG_CLEAR_COUNT_V6 1024
+
+/* Codec Common Registers */
+#define S5P_FIMV_RISC_ON_V6 0x0000
+#define S5P_FIMV_RISC2HOST_INT_V6 0x003C
+#define S5P_FIMV_HOST2RISC_INT_V6 0x0044
+#define S5P_FIMV_RISC_BASE_ADDRESS_V6 0x0054
+
+#define S5P_FIMV_MFC_RESET_V6 0x1070
+
+#define S5P_FIMV_HOST2RISC_CMD_V6 0x1100
+#define S5P_FIMV_H2R_CMD_EMPTY_V6 0
+#define S5P_FIMV_H2R_CMD_SYS_INIT_V6 1
+#define S5P_FIMV_H2R_CMD_OPEN_INSTANCE_V6 2
+#define S5P_FIMV_CH_SEQ_HEADER_V6 3
+#define S5P_FIMV_CH_INIT_BUFS_V6 4
+#define S5P_FIMV_CH_FRAME_START_V6 5
+#define S5P_FIMV_H2R_CMD_CLOSE_INSTANCE_V6 6
+#define S5P_FIMV_H2R_CMD_SLEEP_V6 7
+#define S5P_FIMV_H2R_CMD_WAKEUP_V6 8
+#define S5P_FIMV_CH_LAST_FRAME_V6 9
+#define S5P_FIMV_H2R_CMD_FLUSH_V6 10
+/* RMVME: REALLOC used? */
+#define S5P_FIMV_CH_FRAME_START_REALLOC_V6 5
+
+#define S5P_FIMV_RISC2HOST_CMD_V6 0x1104
+#define S5P_FIMV_R2H_CMD_EMPTY_V6 0
+#define S5P_FIMV_R2H_CMD_SYS_INIT_RET_V6 1
+#define S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET_V6 2
+#define S5P_FIMV_R2H_CMD_SEQ_DONE_RET_V6 3
+#define S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET_V6 4
+
+#define S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET_V6 6
+#define S5P_FIMV_R2H_CMD_SLEEP_RET_V6 7
+#define S5P_FIMV_R2H_CMD_WAKEUP_RET_V6 8
+#define S5P_FIMV_R2H_CMD_COMPLETE_SEQ_RET_V6 9
+#define S5P_FIMV_R2H_CMD_DPB_FLUSH_RET_V6 10
+#define S5P_FIMV_R2H_CMD_NAL_ABORT_RET_V6 11
+#define S5P_FIMV_R2H_CMD_FW_STATUS_RET_V6 12
+#define S5P_FIMV_R2H_CMD_FRAME_DONE_RET_V6 13
+#define S5P_FIMV_R2H_CMD_FIELD_DONE_RET_V6 14
+#define S5P_FIMV_R2H_CMD_SLICE_DONE_RET_V6 15
+#define S5P_FIMV_R2H_CMD_ENC_BUFFER_FUL_RET_V6 16
+#define S5P_FIMV_R2H_CMD_ERR_RET_V6 32
+
+#define S5P_FIMV_MFC_BUS_RESET_CTRL 0x7110
+#define S5P_FIMV_FW_VERSION_V6 0xf000
+
+#define S5P_FIMV_INSTANCE_ID_V6 0xf008
+#define S5P_FIMV_CODEC_TYPE_V6 0xf00c
+#define S5P_FIMV_CONTEXT_MEM_ADDR_V6 0xf014
+#define S5P_FIMV_CONTEXT_MEM_SIZE_V6 0xf018
+#define S5P_FIMV_PIXEL_FORMAT_V6 0xf020
+
+#define S5P_FIMV_METADATA_ENABLE_V6 0xf024
+#define S5P_FIMV_DBG_BUFFER_ADDR_V6 0xf030
+#define S5P_FIMV_DBG_BUFFER_SIZE_V6 0xf034
+#define S5P_FIMV_RET_INSTANCE_ID_V6 0xf070
+
+#define S5P_FIMV_ERROR_CODE_V6 0xf074
+#define S5P_FIMV_ERR_WARNINGS_START_V6 160
+#define S5P_FIMV_ERR_DEC_MASK_V6 0xffff
+#define S5P_FIMV_ERR_DEC_SHIFT_V6 0
+#define S5P_FIMV_ERR_DSPL_MASK_V6 0xffff0000
+#define S5P_FIMV_ERR_DSPL_SHIFT_V6 16
+
+#define S5P_FIMV_DBG_BUFFER_OUTPUT_SIZE_V6 0xf078
+#define S5P_FIMV_METADATA_STATUS_V6 0xf07C
+#define S5P_FIMV_METADATA_ADDR_MB_INFO_V6 0xf080
+#define S5P_FIMV_METADATA_SIZE_MB_INFO_V6 0xf084
+
+/* Decoder Registers */
+#define S5P_FIMV_D_CRC_CTRL_V6 0xf0b0
+#define S5P_FIMV_D_DEC_OPTIONS_V6 0xf0b4
+#define S5P_FIMV_D_OPT_FMO_ASO_CTRL_MASK_V6 4
+#define S5P_FIMV_D_OPT_DDELAY_EN_SHIFT_V6 3
+#define S5P_FIMV_D_OPT_LF_CTRL_SHIFT_V6 1
+#define S5P_FIMV_D_OPT_LF_CTRL_MASK_V6 0x3
+#define S5P_FIMV_D_OPT_TILE_MODE_SHIFT_V6 0
+
+#define S5P_FIMV_D_DISPLAY_DELAY_V6 0xf0b8
+
+#define S5P_FIMV_D_SET_FRAME_WIDTH_V6 0xf0bc
+#define S5P_FIMV_D_SET_FRAME_HEIGHT_V6 0xf0c0
+
+#define S5P_FIMV_D_SEI_ENABLE_V6 0xf0c4
+
+/* Buffer setting registers */
+#define S5P_FIMV_D_MIN_NUM_DPB_V6 0xf0f0
+#define S5P_FIMV_D_MIN_LUMA_DPB_SIZE_V6 0xf0f4
+#define S5P_FIMV_D_MIN_CHROMA_DPB_SIZE_V6 0xf0f8
+#define S5P_FIMV_D_MVC_NUM_VIEWS_V6 0xf0fc
+#define S5P_FIMV_D_MIN_NUM_MV_V6 0xf100
+#define S5P_FIMV_D_NUM_DPB_V6 0xf130
+#define S5P_FIMV_D_LUMA_DPB_SIZE_V6 0xf134
+#define S5P_FIMV_D_CHROMA_DPB_SIZE_V6 0xf138
+#define S5P_FIMV_D_MV_BUFFER_SIZE_V6 0xf13c
+
+#define S5P_FIMV_D_LUMA_DPB_V6 0xf140
+#define S5P_FIMV_D_CHROMA_DPB_V6 0xf240
+#define S5P_FIMV_D_MV_BUFFER_V6 0xf340
+
+#define S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V6 0xf440
+#define S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V6 0xf444
+#define S5P_FIMV_D_METADATA_BUFFER_ADDR_V6 0xf448
+#define S5P_FIMV_D_METADATA_BUFFER_SIZE_V6 0xf44c
+#define S5P_FIMV_D_NUM_MV_V6 0xf478
+#define S5P_FIMV_D_CPB_BUFFER_ADDR_V6 0xf4b0
+#define S5P_FIMV_D_CPB_BUFFER_SIZE_V6 0xf4b4
+
+#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_UPPER_V6 0xf4b8
+#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V6 0xf4bc
+#define S5P_FIMV_D_CPB_BUFFER_OFFSET_V6 0xf4c0
+#define S5P_FIMV_D_SLICE_IF_ENABLE_V6 0xf4c4
+#define S5P_FIMV_D_PICTURE_TAG_V6 0xf4c8
+#define S5P_FIMV_D_STREAM_DATA_SIZE_V6 0xf4d0
+#define S5P_FIMV_D_INIT_BUFFER_OPTIONS_V6 0xf47c
+
+/* Display information register */
+#define S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V6 0xf500
+#define S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V6 0xf504
+
+/* Display status */
+#define S5P_FIMV_D_DISPLAY_STATUS_V6 0xf508
+
+#define S5P_FIMV_D_DISPLAY_LUMA_ADDR_V6 0xf50c
+#define S5P_FIMV_D_DISPLAY_CHROMA_ADDR_V6 0xf510
+
+#define S5P_FIMV_D_DISPLAY_FRAME_TYPE_V6 0xf514
+
+#define S5P_FIMV_D_DISPLAY_CROP_INFO1_V6 0xf518
+#define S5P_FIMV_D_DISPLAY_CROP_INFO2_V6 0xf51c
+#define S5P_FIMV_D_DISPLAY_PICTURE_PROFILE_V6 0xf520
+#define S5P_FIMV_D_DISPLAY_LUMA_CRC_TOP_V6 0xf524
+#define S5P_FIMV_D_DISPLAY_CHROMA_CRC_TOP_V6 0xf528
+#define S5P_FIMV_D_DISPLAY_LUMA_CRC_BOT_V6 0xf52c
+#define S5P_FIMV_D_DISPLAY_CHROMA_CRC_BOT_V6 0xf530
+#define S5P_FIMV_D_DISPLAY_ASPECT_RATIO_V6 0xf534
+#define S5P_FIMV_D_DISPLAY_EXTENDED_AR_V6 0xf538
+
+/* Decoded picture information register */
+#define S5P_FIMV_D_DECODED_FRAME_WIDTH_V6 0xf53c
+#define S5P_FIMV_D_DECODED_FRAME_HEIGHT_V6 0xf540
+#define S5P_FIMV_D_DECODED_STATUS_V6 0xf544
+#define S5P_FIMV_DEC_CRC_GEN_MASK_V6 0x1
+#define S5P_FIMV_DEC_CRC_GEN_SHIFT_V6 6
+
+#define S5P_FIMV_D_DECODED_LUMA_ADDR_V6 0xf548
+#define S5P_FIMV_D_DECODED_CHROMA_ADDR_V6 0xf54c
+
+#define S5P_FIMV_D_DECODED_FRAME_TYPE_V6 0xf550
+#define S5P_FIMV_DECODE_FRAME_MASK_V6 7
+
+#define S5P_FIMV_D_DECODED_CROP_INFO1_V6 0xf554
+#define S5P_FIMV_D_DECODED_CROP_INFO2_V6 0xf558
+#define S5P_FIMV_D_DECODED_PICTURE_PROFILE_V6 0xf55c
+#define S5P_FIMV_D_DECODED_NAL_SIZE_V6 0xf560
+#define S5P_FIMV_D_DECODED_LUMA_CRC_TOP_V6 0xf564
+#define S5P_FIMV_D_DECODED_CHROMA_CRC_TOP_V6 0xf568
+#define S5P_FIMV_D_DECODED_LUMA_CRC_BOT_V6 0xf56c
+#define S5P_FIMV_D_DECODED_CHROMA_CRC_BOT_V6 0xf570
+
+/* Returned value register for specific setting */
+#define S5P_FIMV_D_RET_PICTURE_TAG_TOP_V6 0xf574
+#define S5P_FIMV_D_RET_PICTURE_TAG_BOT_V6 0xf578
+#define S5P_FIMV_D_RET_PICTURE_TIME_TOP_V6 0xf57c
+#define S5P_FIMV_D_RET_PICTURE_TIME_BOT_V6 0xf580
+#define S5P_FIMV_D_CHROMA_FORMAT_V6 0xf588
+#define S5P_FIMV_D_MPEG4_INFO_V6 0xf58c
+#define S5P_FIMV_D_H264_INFO_V6 0xf590
+
+#define S5P_FIMV_D_METADATA_ADDR_CONCEALED_MB_V6 0xf594
+#define S5P_FIMV_D_METADATA_SIZE_CONCEALED_MB_V6 0xf598
+#define S5P_FIMV_D_METADATA_ADDR_VC1_PARAM_V6 0xf59c
+#define S5P_FIMV_D_METADATA_SIZE_VC1_PARAM_V6 0xf5a0
+#define S5P_FIMV_D_METADATA_ADDR_SEI_NAL_V6 0xf5a4
+#define S5P_FIMV_D_METADATA_SIZE_SEI_NAL_V6 0xf5a8
+#define S5P_FIMV_D_METADATA_ADDR_VUI_V6 0xf5ac
+#define S5P_FIMV_D_METADATA_SIZE_VUI_V6 0xf5b0
+
+#define S5P_FIMV_D_MVC_VIEW_ID_V6 0xf5b4
+
+/* SEI related information */
+#define S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V6 0xf5f0
+#define S5P_FIMV_D_FRAME_PACK_ARRGMENT_ID_V6 0xf5f4
+#define S5P_FIMV_D_FRAME_PACK_SEI_INFO_V6 0xf5f8
+#define S5P_FIMV_D_FRAME_PACK_GRID_POS_V6 0xf5fc
+
+/* Encoder Registers */
+#define S5P_FIMV_E_FRAME_WIDTH_V6 0xf770
+#define S5P_FIMV_E_FRAME_HEIGHT_V6 0xf774
+#define S5P_FIMV_E_CROPPED_FRAME_WIDTH_V6 0xf778
+#define S5P_FIMV_E_CROPPED_FRAME_HEIGHT_V6 0xf77c
+#define S5P_FIMV_E_FRAME_CROP_OFFSET_V6 0xf780
+#define S5P_FIMV_E_ENC_OPTIONS_V6 0xf784
+#define S5P_FIMV_E_PICTURE_PROFILE_V6 0xf788
+#define S5P_FIMV_E_FIXED_PICTURE_QP_V6 0xf790
+
+#define S5P_FIMV_E_RC_CONFIG_V6 0xf794
+#define S5P_FIMV_E_RC_QP_BOUND_V6 0xf798
+#define S5P_FIMV_E_RC_RPARAM_V6 0xf79c
+#define S5P_FIMV_E_MB_RC_CONFIG_V6 0xf7a0
+#define S5P_FIMV_E_PADDING_CTRL_V6 0xf7a4
+#define S5P_FIMV_E_MV_HOR_RANGE_V6 0xf7ac
+#define S5P_FIMV_E_MV_VER_RANGE_V6 0xf7b0
+#define S5P_FIMV_E_MV_RANGE_V6_MASK 0x3fff
+
+#define S5P_FIMV_E_VBV_BUFFER_SIZE_V6 0xf84c
+#define S5P_FIMV_E_VBV_INIT_DELAY_V6 0xf850
+#define S5P_FIMV_E_NUM_DPB_V6 0xf890
+#define S5P_FIMV_E_LUMA_DPB_V6 0xf8c0
+#define S5P_FIMV_E_CHROMA_DPB_V6 0xf904
+#define S5P_FIMV_E_ME_BUFFER_V6 0xf948
+
+#define S5P_FIMV_E_SCRATCH_BUFFER_ADDR_V6 0xf98c
+#define S5P_FIMV_E_SCRATCH_BUFFER_SIZE_V6 0xf990
+#define S5P_FIMV_E_TMV_BUFFER0_V6 0xf994
+#define S5P_FIMV_E_TMV_BUFFER1_V6 0xf998
+#define S5P_FIMV_E_SOURCE_LUMA_ADDR_V6 0xf9f0
+#define S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6 0xf9f4
+#define S5P_FIMV_E_STREAM_BUFFER_ADDR_V6 0xf9f8
+#define S5P_FIMV_E_STREAM_BUFFER_SIZE_V6 0xf9fc
+#define S5P_FIMV_E_ROI_BUFFER_ADDR_V6 0xfA00
+
+#define S5P_FIMV_E_PARAM_CHANGE_V6 0xfa04
+#define S5P_FIMV_E_IR_SIZE_V6 0xfa08
+#define S5P_FIMV_E_GOP_CONFIG_V6 0xfa0c
+#define S5P_FIMV_E_MSLICE_MODE_V6 0xfa10
+#define S5P_FIMV_E_MSLICE_SIZE_MB_V6 0xfa14
+#define S5P_FIMV_E_MSLICE_SIZE_BITS_V6 0xfa18
+#define S5P_FIMV_E_FRAME_INSERTION_V6 0xfa1c
+
+#define S5P_FIMV_E_RC_FRAME_RATE_V6 0xfa20
+#define S5P_FIMV_E_RC_BIT_RATE_V6 0xfa24
+#define S5P_FIMV_E_RC_QP_OFFSET_V6 0xfa28
+#define S5P_FIMV_E_RC_ROI_CTRL_V6 0xfa2c
+#define S5P_FIMV_E_PICTURE_TAG_V6 0xfa30
+#define S5P_FIMV_E_BIT_COUNT_ENABLE_V6 0xfa34
+#define S5P_FIMV_E_MAX_BIT_COUNT_V6 0xfa38
+#define S5P_FIMV_E_MIN_BIT_COUNT_V6 0xfa3c
+
+#define S5P_FIMV_E_METADATA_BUFFER_ADDR_V6 0xfa40
+#define S5P_FIMV_E_METADATA_BUFFER_SIZE_V6 0xfa44
+#define S5P_FIMV_E_STREAM_SIZE_V6 0xfa80
+#define S5P_FIMV_E_SLICE_TYPE_V6 0xfa84
+#define S5P_FIMV_E_PICTURE_COUNT_V6 0xfa88
+#define S5P_FIMV_E_RET_PICTURE_TAG_V6 0xfa8c
+#define S5P_FIMV_E_STREAM_BUFFER_WRITE_POINTER_V6 0xfa90
+
+#define S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6 0xfa94
+#define S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6 0xfa98
+#define S5P_FIMV_E_RECON_LUMA_DPB_ADDR_V6 0xfa9c
+#define S5P_FIMV_E_RECON_CHROMA_DPB_ADDR_V6 0xfaa0
+#define S5P_FIMV_E_METADATA_ADDR_ENC_SLICE_V6 0xfaa4
+#define S5P_FIMV_E_METADATA_SIZE_ENC_SLICE_V6 0xfaa8
+
+#define S5P_FIMV_E_MPEG4_OPTIONS_V6 0xfb10
+#define S5P_FIMV_E_MPEG4_HEC_PERIOD_V6 0xfb14
+#define S5P_FIMV_E_ASPECT_RATIO_V6 0xfb50
+#define S5P_FIMV_E_EXTENDED_SAR_V6 0xfb54
+
+#define S5P_FIMV_E_H264_OPTIONS_V6 0xfb58
+#define S5P_FIMV_E_H264_LF_ALPHA_OFFSET_V6 0xfb5c
+#define S5P_FIMV_E_H264_LF_BETA_OFFSET_V6 0xfb60
+#define S5P_FIMV_E_H264_I_PERIOD_V6 0xfb64
+
+#define S5P_FIMV_E_H264_FMO_SLICE_GRP_MAP_TYPE_V6 0xfb68
+#define S5P_FIMV_E_H264_FMO_NUM_SLICE_GRP_MINUS1_V6 0xfb6c
+#define S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_DIR_V6 0xfb70
+#define S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_RATE_MINUS1_V6 0xfb74
+#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_0_V6 0xfb78
+#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_1_V6 0xfb7c
+#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_2_V6 0xfb80
+#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_3_V6 0xfb84
+
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_0_V6 0xfb88
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_1_V6 0xfb8c
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_2_V6 0xfb90
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_3_V6 0xfb94
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_4_V6 0xfb98
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_5_V6 0xfb9c
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_6_V6 0xfba0
+#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_7_V6 0xfba4
+
+#define S5P_FIMV_E_H264_CHROMA_QP_OFFSET_V6 0xfba8
+#define S5P_FIMV_E_H264_NUM_T_LAYER_V6 0xfbac
+
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER0_V6 0xfbb0
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER1_V6 0xfbb4
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER2_V6 0xfbb8
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER3_V6 0xfbbc
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER4_V6 0xfbc0
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER5_V6 0xfbc4
+#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER6_V6 0xfbc8
+
+#define S5P_FIMV_E_H264_FRAME_PACKING_SEI_INFO_V6 0xfc4c
+#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_SIDE_BY_SIDE_V6 0
+#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_TOP_BOTTOM_V6 1
+#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_TEMPORAL_V6 2
+
+#define S5P_FIMV_E_MVC_FRAME_QP_VIEW1_V6 0xfd40
+#define S5P_FIMV_E_MVC_RC_FRAME_RATE_VIEW1_V6 0xfd44
+#define S5P_FIMV_E_MVC_RC_BIT_RATE_VIEW1_V6 0xfd48
+#define S5P_FIMV_E_MVC_RC_QBOUND_VIEW1_V6 0xfd4c
+#define S5P_FIMV_E_MVC_RC_RPARA_VIEW1_V6 0xfd50
+#define S5P_FIMV_E_MVC_INTER_VIEW_PREDICTION_ON_V6 0xfd80
+
+/* Codec numbers */
+#define S5P_FIMV_CODEC_NONE_V6 -1
+
+
+#define S5P_FIMV_CODEC_H264_DEC_V6 0
+#define S5P_FIMV_CODEC_H264_MVC_DEC_V6 1
+
+#define S5P_FIMV_CODEC_MPEG4_DEC_V6 3
+#define S5P_FIMV_CODEC_FIMV1_DEC_V6 4
+#define S5P_FIMV_CODEC_FIMV2_DEC_V6 5
+#define S5P_FIMV_CODEC_FIMV3_DEC_V6 6
+#define S5P_FIMV_CODEC_FIMV4_DEC_V6 7
+#define S5P_FIMV_CODEC_H263_DEC_V6 8
+#define S5P_FIMV_CODEC_VC1RCV_DEC_V6 9
+#define S5P_FIMV_CODEC_VC1_DEC_V6 10
+/* FIXME: Add 11~12 */
+#define S5P_FIMV_CODEC_MPEG2_DEC_V6 13
+#define S5P_FIMV_CODEC_VP8_DEC_V6 14
+/* FIXME: Add 15~16 */
+#define S5P_FIMV_CODEC_H264_ENC_V6 20
+#define S5P_FIMV_CODEC_H264_MVC_ENC_V6 21
+
+#define S5P_FIMV_CODEC_MPEG4_ENC_V6 23
+#define S5P_FIMV_CODEC_H263_ENC_V6 24
+
+#define S5P_FIMV_NV12M_HALIGN_V6 16
+#define S5P_FIMV_NV12MT_HALIGN_V6 16
+#define S5P_FIMV_NV12MT_VALIGN_V6 16
+
+#define S5P_FIMV_TMV_BUFFER_ALIGN_V6 16
+#define S5P_FIMV_LUMA_DPB_BUFFER_ALIGN_V6 256
+#define S5P_FIMV_CHROMA_DPB_BUFFER_ALIGN_V6 256
+#define S5P_FIMV_ME_BUFFER_ALIGN_V6 256
+#define S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6 256
+
+#define S5P_FIMV_LUMA_MB_TO_PIXEL_V6 256
+#define S5P_FIMV_CHROMA_MB_TO_PIXEL_V6 128
+#define S5P_FIMV_NUM_TMV_BUFFERS_V6 2
+
+#define S5P_FIMV_MAX_FRAME_SIZE_V6 (2 * SZ_1M)
+#define S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6 16
+#define S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6 16
+
+/* Buffer size requirements defined by hardware */
+#define S5P_FIMV_TMV_BUFFER_SIZE_V6(w, h) (((w) + 1) * ((h) + 3) * 8)
+#define S5P_FIMV_ME_BUFFER_SIZE_V6(imw, imh, mbw, mbh) \
+ (((((imw + 127) / 64) * 16) * DIV_ROUND_UP(imh, 64) * 256) + \
+ (DIV_ROUND_UP((mbw) * (mbh), 32) * 16))
+#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V6(w, h) (((w) * 192) + 64)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(w, h) \
+ ((w) * 144 + 8192 * (h) + 49216 + 1048576)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_VC1_DEC_V6(w, h) \
+ (2096 * ((w) + (h) + 1))
+#define S5P_FIMV_SCRATCH_BUF_SIZE_H263_DEC_V6(w, h) \
+ S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(w, h)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V6(w, h) \
+ ((w) * 32 + (h) * 128 + (((w) + 1) / 2) * 64 + 2112)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V6(w, h) \
+ (((w) * 64) + (((w) + 1) * 16) + (4096 * 16))
+#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_ENC_V6(w, h) \
+ (((w) * 16) + (((w) + 1) * 16))
+
+/* MFC Context buffer sizes */
+#define MFC_CTX_BUF_SIZE_V6 (28 * SZ_1K) /* 28KB */
+#define MFC_H264_DEC_CTX_BUF_SIZE_V6 (2 * SZ_1M) /* 2MB */
+#define MFC_OTHER_DEC_CTX_BUF_SIZE_V6 (20 * SZ_1K) /* 20KB */
+#define MFC_H264_ENC_CTX_BUF_SIZE_V6 (100 * SZ_1K) /* 100KB */
+#define MFC_OTHER_ENC_CTX_BUF_SIZE_V6 (12 * SZ_1K) /* 12KB */
+
+/* MFCv6 variant defines */
+#define MAX_FW_SIZE_V6 (SZ_512K) /* 512KB */
+#define MAX_CPB_SIZE_V6 (3 * SZ_1M) /* 3MB */
+#define MFC_VERSION_V6 0x61
+#define MFC_NUM_PORTS_V6 1
+
+#endif /* _REGS_FIMV_V6_H */
diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h
new file mode 100644
index 0000000000..4a7adfdaa3
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register definition file for Samsung MFC V7.x Interface (FIMV) driver
+ *
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef _REGS_MFC_V7_H
+#define _REGS_MFC_V7_H
+
+#include "regs-mfc-v6.h"
+
+/* Additional features of v7 */
+#define S5P_FIMV_CODEC_VP8_ENC_V7 25
+
+/* Additional registers for v7 */
+#define S5P_FIMV_E_SOURCE_FIRST_ADDR_V7 0xf9e0
+#define S5P_FIMV_E_SOURCE_SECOND_ADDR_V7 0xf9e4
+#define S5P_FIMV_E_SOURCE_THIRD_ADDR_V7 0xf9e8
+#define S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7 0xf9ec
+#define S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7 0xf9f0
+#define S5P_FIMV_E_SOURCE_THIRD_STRIDE_V7 0xf9f4
+
+#define S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7 0xfa70
+#define S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7 0xfa74
+
+#define S5P_FIMV_E_VP8_OPTIONS_V7 0xfdb0
+#define S5P_FIMV_E_VP8_FILTER_OPTIONS_V7 0xfdb4
+#define S5P_FIMV_E_VP8_GOLDEN_FRAME_OPTION_V7 0xfdb8
+#define S5P_FIMV_E_VP8_NUM_T_LAYER_V7 0xfdc4
+
+/* MFCv7 variant defines */
+#define MAX_FW_SIZE_V7 (SZ_512K) /* 512KB */
+#define MAX_CPB_SIZE_V7 (3 * SZ_1M) /* 3MB */
+#define MFC_VERSION_V7 0x72
+#define MFC_NUM_PORTS_V7 1
+
+#define MFC_LUMA_PAD_BYTES_V7 256
+#define MFC_CHROMA_PAD_BYTES_V7 128
+
+/* MFCv7 Context buffer sizes */
+#define MFC_CTX_BUF_SIZE_V7 (30 * SZ_1K) /* 30KB */
+#define MFC_H264_DEC_CTX_BUF_SIZE_V7 (2 * SZ_1M) /* 2MB */
+#define MFC_OTHER_DEC_CTX_BUF_SIZE_V7 (20 * SZ_1K) /* 20KB */
+#define MFC_H264_ENC_CTX_BUF_SIZE_V7 (100 * SZ_1K) /* 100KB */
+#define MFC_OTHER_ENC_CTX_BUF_SIZE_V7 (10 * SZ_1K) /* 10KB */
+
+/* Buffer size defines */
+#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7(w, h) \
+ (SZ_1M + ((w) * 144) + (8192 * (h)) + 49216)
+
+#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7(w, h) \
+ (((w) * 48) + 8192 + ((((w) + 1) / 2) * 128) + 144 + \
+ ((((((w) * 16) * ((h) * 16)) * 3) / 2) * 4))
+
+#endif /*_REGS_MFC_V7_H*/
diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h
new file mode 100644
index 0000000000..162e3c7e92
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register definition file for Samsung MFC V8.x Interface (FIMV) driver
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef _REGS_MFC_V8_H
+#define _REGS_MFC_V8_H
+
+#include <linux/sizes.h>
+#include "regs-mfc-v7.h"
+
+/* Additional registers for v8 */
+#define S5P_FIMV_D_MVC_NUM_VIEWS_V8 0xf104
+#define S5P_FIMV_D_MIN_SCRATCH_BUFFER_SIZE_V8 0xf108
+#define S5P_FIMV_D_FIRST_PLANE_DPB_SIZE_V8 0xf144
+#define S5P_FIMV_D_SECOND_PLANE_DPB_SIZE_V8 0xf148
+#define S5P_FIMV_D_MV_BUFFER_SIZE_V8 0xf150
+
+#define S5P_FIMV_D_FIRST_PLANE_DPB_STRIDE_SIZE_V8 0xf138
+#define S5P_FIMV_D_SECOND_PLANE_DPB_STRIDE_SIZE_V8 0xf13c
+
+#define S5P_FIMV_D_FIRST_PLANE_DPB_V8 0xf160
+#define S5P_FIMV_D_SECOND_PLANE_DPB_V8 0xf260
+#define S5P_FIMV_D_MV_BUFFER_V8 0xf460
+
+#define S5P_FIMV_D_NUM_MV_V8 0xf134
+#define S5P_FIMV_D_INIT_BUFFER_OPTIONS_V8 0xf154
+
+#define S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V8 0xf560
+#define S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V8 0xf564
+
+#define S5P_FIMV_D_CPB_BUFFER_ADDR_V8 0xf5b0
+#define S5P_FIMV_D_CPB_BUFFER_SIZE_V8 0xf5b4
+#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V8 0xf5bc
+#define S5P_FIMV_D_CPB_BUFFER_OFFSET_V8 0xf5c0
+#define S5P_FIMV_D_SLICE_IF_ENABLE_V8 0xf5c4
+#define S5P_FIMV_D_STREAM_DATA_SIZE_V8 0xf5d0
+
+/* Display information register */
+#define S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V8 0xf600
+#define S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V8 0xf604
+
+/* Display status */
+#define S5P_FIMV_D_DISPLAY_STATUS_V8 0xf608
+
+#define S5P_FIMV_D_DISPLAY_FIRST_PLANE_ADDR_V8 0xf60c
+#define S5P_FIMV_D_DISPLAY_SECOND_PLANE_ADDR_V8 0xf610
+
+#define S5P_FIMV_D_DISPLAY_FRAME_TYPE_V8 0xf618
+#define S5P_FIMV_D_DISPLAY_CROP_INFO1_V8 0xf61c
+#define S5P_FIMV_D_DISPLAY_CROP_INFO2_V8 0xf620
+#define S5P_FIMV_D_DISPLAY_PICTURE_PROFILE_V8 0xf624
+
+/* Decoded picture information register */
+#define S5P_FIMV_D_DECODED_STATUS_V8 0xf644
+#define S5P_FIMV_D_DECODED_FIRST_PLANE_ADDR_V8 0xf648
+#define S5P_FIMV_D_DECODED_SECOND_PLANE_ADDR_V8 0xf64c
+#define S5P_FIMV_D_DECODED_THIRD_PLANE_ADDR_V8 0xf650
+#define S5P_FIMV_D_DECODED_FRAME_TYPE_V8 0xf654
+#define S5P_FIMV_D_DECODED_NAL_SIZE_V8 0xf664
+
+/* Returned value register for specific setting */
+#define S5P_FIMV_D_RET_PICTURE_TAG_TOP_V8 0xf674
+#define S5P_FIMV_D_RET_PICTURE_TAG_BOT_V8 0xf678
+#define S5P_FIMV_D_MVC_VIEW_ID_V8 0xf6d8
+
+/* SEI related information */
+#define S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V8 0xf6dc
+
+/* Encoder Registers */
+#define S5P_FIMV_E_FIXED_PICTURE_QP_V8 0xf794
+#define S5P_FIMV_E_RC_CONFIG_V8 0xf798
+#define S5P_FIMV_E_RC_QP_BOUND_V8 0xf79c
+#define S5P_FIMV_E_RC_RPARAM_V8 0xf7a4
+#define S5P_FIMV_E_MB_RC_CONFIG_V8 0xf7a8
+#define S5P_FIMV_E_PADDING_CTRL_V8 0xf7ac
+#define S5P_FIMV_E_MV_HOR_RANGE_V8 0xf7b4
+#define S5P_FIMV_E_MV_VER_RANGE_V8 0xf7b8
+
+#define S5P_FIMV_E_VBV_BUFFER_SIZE_V8 0xf78c
+#define S5P_FIMV_E_VBV_INIT_DELAY_V8 0xf790
+#define S5P_FIMV_E_MIN_SCRATCH_BUFFER_SIZE_V8 0xf894
+
+#define S5P_FIMV_E_ASPECT_RATIO_V8 0xfb4c
+#define S5P_FIMV_E_EXTENDED_SAR_V8 0xfb50
+#define S5P_FIMV_E_H264_OPTIONS_V8 0xfb54
+
+/* MFCv8 Context buffer sizes */
+#define MFC_CTX_BUF_SIZE_V8 (36 * SZ_1K) /* 36KB */
+#define MFC_H264_DEC_CTX_BUF_SIZE_V8 (2 * SZ_1M) /* 2MB */
+#define MFC_OTHER_DEC_CTX_BUF_SIZE_V8 (20 * SZ_1K) /* 20KB */
+#define MFC_H264_ENC_CTX_BUF_SIZE_V8 (100 * SZ_1K) /* 100KB */
+#define MFC_OTHER_ENC_CTX_BUF_SIZE_V8 (10 * SZ_1K) /* 10KB */
+
+/* Buffer size defines */
+#define S5P_FIMV_TMV_BUFFER_SIZE_V8(w, h) (((w) + 1) * ((h) + 1) * 8)
+
+#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V8(w, h) (((w) * 704) + 2176)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V8(w, h) \
+ (((w) * 576 + (h) * 128) + 4128)
+
+#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V8(w, h) \
+ (((w) * 592) + 2336)
+#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V8(w, h) \
+ (((w) * 576) + 10512 + \
+ ((((((w) * 16) * ((h) * 16)) * 3) / 2) * 4))
+#define S5P_FIMV_ME_BUFFER_SIZE_V8(imw, imh, mbw, mbh) \
+ ((DIV_ROUND_UP((mbw * 16), 64) * DIV_ROUND_UP((mbh * 16), 64) * 256) \
+ + (DIV_ROUND_UP((mbw) * (mbh), 32) * 16))
+
+/* BUffer alignment defines */
+#define S5P_FIMV_D_ALIGN_PLANE_SIZE_V8 64
+
+/* MFCv8 variant defines */
+#define MAX_FW_SIZE_V8 (SZ_512K) /* 512KB */
+#define MAX_CPB_SIZE_V8 (3 * SZ_1M) /* 3MB */
+#define MFC_VERSION_V8 0x80
+#define MFC_NUM_PORTS_V8 1
+
+#endif /*_REGS_MFC_V8_H*/
diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h
new file mode 100644
index 0000000000..9171e8181c
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h
@@ -0,0 +1,459 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Register definition file for Samsung MFC V5.1 Interface (FIMV) driver
+ *
+ * Kamil Debski, Copyright (c) 2010 Samsung Electronics
+ * http://www.samsung.com/
+*/
+
+#ifndef _REGS_FIMV_H
+#define _REGS_FIMV_H
+
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+
+#define S5P_FIMV_REG_SIZE (S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR)
+#define S5P_FIMV_REG_COUNT ((S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) / 4)
+
+/* Number of bits that the buffer address should be shifted for particular
+ * MFC buffers. */
+#define S5P_FIMV_START_ADDR 0x0000
+#define S5P_FIMV_END_ADDR 0xe008
+
+#define S5P_FIMV_SW_RESET 0x0000
+#define S5P_FIMV_RISC_HOST_INT 0x0008
+
+/* Command from HOST to RISC */
+#define S5P_FIMV_HOST2RISC_CMD 0x0030
+#define S5P_FIMV_HOST2RISC_ARG1 0x0034
+#define S5P_FIMV_HOST2RISC_ARG2 0x0038
+#define S5P_FIMV_HOST2RISC_ARG3 0x003c
+#define S5P_FIMV_HOST2RISC_ARG4 0x0040
+
+/* Command from RISC to HOST */
+#define S5P_FIMV_RISC2HOST_CMD 0x0044
+#define S5P_FIMV_RISC2HOST_CMD_MASK 0x1FFFF
+#define S5P_FIMV_RISC2HOST_ARG1 0x0048
+#define S5P_FIMV_RISC2HOST_ARG2 0x004c
+#define S5P_FIMV_RISC2HOST_ARG3 0x0050
+#define S5P_FIMV_RISC2HOST_ARG4 0x0054
+
+#define S5P_FIMV_FW_VERSION 0x0058
+#define S5P_FIMV_SYS_MEM_SZ 0x005c
+#define S5P_FIMV_FW_STATUS 0x0080
+
+/* Memory controller register */
+#define S5P_FIMV_MC_DRAMBASE_ADR_A 0x0508
+#define S5P_FIMV_MC_DRAMBASE_ADR_B 0x050c
+#define S5P_FIMV_MC_STATUS 0x0510
+
+/* Common register */
+#define S5P_FIMV_COMMON_BASE_A 0x0600
+#define S5P_FIMV_COMMON_BASE_B 0x0700
+
+/* Decoder */
+#define S5P_FIMV_DEC_CHROMA_ADR (S5P_FIMV_COMMON_BASE_A)
+#define S5P_FIMV_DEC_LUMA_ADR (S5P_FIMV_COMMON_BASE_B)
+
+/* H.264 decoding */
+#define S5P_FIMV_H264_VERT_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c)
+ /* vertical neighbor motion vector */
+#define S5P_FIMV_H264_NB_IP_ADR (S5P_FIMV_COMMON_BASE_A + 0x90)
+ /* neighbor pixels for intra pred */
+#define S5P_FIMV_H264_MV_ADR (S5P_FIMV_COMMON_BASE_B + 0x80)
+ /* H264 motion vector */
+
+/* MPEG4 decoding */
+#define S5P_FIMV_MPEG4_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c)
+ /* neighbor AC/DC coeff. */
+#define S5P_FIMV_MPEG4_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90)
+ /* upper neighbor motion vector */
+#define S5P_FIMV_MPEG4_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94)
+ /* subseq. anchor motion vector */
+#define S5P_FIMV_MPEG4_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98)
+ /* overlap transform line */
+#define S5P_FIMV_MPEG4_SP_ADR (S5P_FIMV_COMMON_BASE_A + 0xa8)
+ /* syntax parser */
+
+/* H.263 decoding */
+#define S5P_FIMV_H263_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c)
+#define S5P_FIMV_H263_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90)
+#define S5P_FIMV_H263_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94)
+#define S5P_FIMV_H263_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98)
+
+/* VC-1 decoding */
+#define S5P_FIMV_VC1_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c)
+#define S5P_FIMV_VC1_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90)
+#define S5P_FIMV_VC1_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94)
+#define S5P_FIMV_VC1_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98)
+#define S5P_FIMV_VC1_BITPLANE3_ADR (S5P_FIMV_COMMON_BASE_A + 0x9c)
+ /* bitplane3 */
+#define S5P_FIMV_VC1_BITPLANE2_ADR (S5P_FIMV_COMMON_BASE_A + 0xa0)
+ /* bitplane2 */
+#define S5P_FIMV_VC1_BITPLANE1_ADR (S5P_FIMV_COMMON_BASE_A + 0xa4)
+ /* bitplane1 */
+
+/* Encoder */
+#define S5P_FIMV_ENC_REF0_LUMA_ADR (S5P_FIMV_COMMON_BASE_A + 0x1c)
+#define S5P_FIMV_ENC_REF1_LUMA_ADR (S5P_FIMV_COMMON_BASE_A + 0x20)
+ /* reconstructed luma */
+#define S5P_FIMV_ENC_REF0_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B)
+#define S5P_FIMV_ENC_REF1_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x04)
+ /* reconstructed chroma */
+#define S5P_FIMV_ENC_REF2_LUMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x10)
+#define S5P_FIMV_ENC_REF2_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x08)
+#define S5P_FIMV_ENC_REF3_LUMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x14)
+#define S5P_FIMV_ENC_REF3_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x0c)
+
+/* H.264 encoding */
+#define S5P_FIMV_H264_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A)
+ /* upper motion vector */
+#define S5P_FIMV_H264_NBOR_INFO_ADR (S5P_FIMV_COMMON_BASE_A + 0x04)
+ /* entropy engine's neighbor info. */
+#define S5P_FIMV_H264_UP_INTRA_MD_ADR (S5P_FIMV_COMMON_BASE_A + 0x08)
+ /* upper intra MD */
+#define S5P_FIMV_H264_COZERO_FLAG_ADR (S5P_FIMV_COMMON_BASE_A + 0x10)
+ /* direct cozero flag */
+#define S5P_FIMV_H264_UP_INTRA_PRED_ADR (S5P_FIMV_COMMON_BASE_B + 0x40)
+ /* upper intra PRED */
+
+/* H.263 encoding */
+#define S5P_FIMV_H263_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A)
+ /* upper motion vector */
+#define S5P_FIMV_H263_ACDC_COEF_ADR (S5P_FIMV_COMMON_BASE_A + 0x04)
+ /* upper Q coeff. */
+
+/* MPEG4 encoding */
+#define S5P_FIMV_MPEG4_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A)
+ /* upper motion vector */
+#define S5P_FIMV_MPEG4_ACDC_COEF_ADR (S5P_FIMV_COMMON_BASE_A + 0x04)
+ /* upper Q coeff. */
+#define S5P_FIMV_MPEG4_COZERO_FLAG_ADR (S5P_FIMV_COMMON_BASE_A + 0x10)
+ /* direct cozero flag */
+
+#define S5P_FIMV_ENC_REF_B_LUMA_ADR 0x062c /* ref B Luma addr */
+#define S5P_FIMV_ENC_REF_B_CHROMA_ADR 0x0630 /* ref B Chroma addr */
+
+#define S5P_FIMV_ENC_CUR_LUMA_ADR 0x0718 /* current Luma addr */
+#define S5P_FIMV_ENC_CUR_CHROMA_ADR 0x071C /* current Chroma addr */
+
+/* Codec common register */
+#define S5P_FIMV_ENC_HSIZE_PX 0x0818 /* frame width at encoder */
+#define S5P_FIMV_ENC_VSIZE_PX 0x081c /* frame height at encoder */
+#define S5P_FIMV_ENC_PROFILE 0x0830 /* profile register */
+#define S5P_FIMV_ENC_PROFILE_H264_MAIN 0
+#define S5P_FIMV_ENC_PROFILE_H264_HIGH 1
+#define S5P_FIMV_ENC_PROFILE_H264_BASELINE 2
+#define S5P_FIMV_ENC_PROFILE_H264_CONSTRAINED_BASELINE 3
+#define S5P_FIMV_ENC_PROFILE_MPEG4_SIMPLE 0
+#define S5P_FIMV_ENC_PROFILE_MPEG4_ADVANCED_SIMPLE 1
+#define S5P_FIMV_ENC_PIC_STRUCT 0x083c /* picture field/frame flag */
+#define S5P_FIMV_ENC_LF_CTRL 0x0848 /* loop filter control */
+#define S5P_FIMV_ENC_ALPHA_OFF 0x084c /* loop filter alpha offset */
+#define S5P_FIMV_ENC_BETA_OFF 0x0850 /* loop filter beta offset */
+#define S5P_FIMV_MR_BUSIF_CTRL 0x0854 /* hidden, bus interface ctrl */
+#define S5P_FIMV_ENC_PXL_CACHE_CTRL 0x0a00 /* pixel cache control */
+
+/* Channel & stream interface register */
+#define S5P_FIMV_SI_RTN_CHID 0x2000 /* Return CH inst ID register */
+#define S5P_FIMV_SI_CH0_INST_ID 0x2040 /* codec instance ID */
+#define S5P_FIMV_SI_CH1_INST_ID 0x2080 /* codec instance ID */
+/* Decoder */
+#define S5P_FIMV_SI_VRESOL 0x2004 /* vertical res of decoder */
+#define S5P_FIMV_SI_HRESOL 0x2008 /* horizontal res of decoder */
+#define S5P_FIMV_SI_BUF_NUMBER 0x200c /* number of frames in the
+ decoded pic */
+#define S5P_FIMV_SI_DISPLAY_Y_ADR 0x2010 /* luma addr of displayed pic */
+#define S5P_FIMV_SI_DISPLAY_C_ADR 0x2014 /* chroma addrof displayed pic */
+
+#define S5P_FIMV_SI_CONSUMED_BYTES 0x2018 /* Consumed number of bytes to
+ decode a frame */
+#define S5P_FIMV_SI_DISPLAY_STATUS 0x201c /* status of decoded picture */
+
+#define S5P_FIMV_SI_DECODE_Y_ADR 0x2024 /* luma addr of decoded pic */
+#define S5P_FIMV_SI_DECODE_C_ADR 0x2028 /* chroma addrof decoded pic */
+#define S5P_FIMV_SI_DECODE_STATUS 0x202c /* status of decoded picture */
+
+#define S5P_FIMV_SI_CH0_SB_ST_ADR 0x2044 /* start addr of stream buf */
+#define S5P_FIMV_SI_CH0_SB_FRM_SIZE 0x2048 /* size of stream buf */
+#define S5P_FIMV_SI_CH0_DESC_ADR 0x204c /* addr of descriptor buf */
+#define S5P_FIMV_SI_CH0_CPB_SIZE 0x2058 /* max size of coded pic. buf */
+#define S5P_FIMV_SI_CH0_DESC_SIZE 0x205c /* max size of descriptor buf */
+
+#define S5P_FIMV_SI_CH1_SB_ST_ADR 0x2084 /* start addr of stream buf */
+#define S5P_FIMV_SI_CH1_SB_FRM_SIZE 0x2088 /* size of stream buf */
+#define S5P_FIMV_SI_CH1_DESC_ADR 0x208c /* addr of descriptor buf */
+#define S5P_FIMV_SI_CH1_CPB_SIZE 0x2098 /* max size of coded pic. buf */
+#define S5P_FIMV_SI_CH1_DESC_SIZE 0x209c /* max size of descriptor buf */
+
+#define S5P_FIMV_CRC_LUMA0 0x2030 /* luma crc data per frame
+ (top field) */
+#define S5P_FIMV_CRC_CHROMA0 0x2034 /* chroma crc data per frame
+ (top field) */
+#define S5P_FIMV_CRC_LUMA1 0x2038 /* luma crc data per bottom
+ field */
+#define S5P_FIMV_CRC_CHROMA1 0x203c /* chroma crc data per bottom
+ field */
+
+/* Display status */
+#define S5P_FIMV_DEC_STATUS_DECODING_ONLY 0
+#define S5P_FIMV_DEC_STATUS_DECODING_DISPLAY 1
+#define S5P_FIMV_DEC_STATUS_DISPLAY_ONLY 2
+#define S5P_FIMV_DEC_STATUS_DECODING_EMPTY 3
+#define S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK 7
+#define S5P_FIMV_DEC_STATUS_PROGRESSIVE (0<<3)
+#define S5P_FIMV_DEC_STATUS_INTERLACE (1<<3)
+#define S5P_FIMV_DEC_STATUS_INTERLACE_MASK (1<<3)
+#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_TWO (0<<4)
+#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_FOUR (1<<4)
+#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_MASK (1<<4)
+#define S5P_FIMV_DEC_STATUS_CRC_GENERATED (1<<5)
+#define S5P_FIMV_DEC_STATUS_CRC_NOT_GENERATED (0<<5)
+#define S5P_FIMV_DEC_STATUS_CRC_MASK (1<<5)
+
+#define S5P_FIMV_DEC_STATUS_RESOLUTION_MASK (3<<4)
+#define S5P_FIMV_DEC_STATUS_RESOLUTION_INC (1<<4)
+#define S5P_FIMV_DEC_STATUS_RESOLUTION_DEC (2<<4)
+#define S5P_FIMV_DEC_STATUS_RESOLUTION_SHIFT 4
+
+/* Decode frame address */
+#define S5P_FIMV_DECODE_Y_ADR 0x2024
+#define S5P_FIMV_DECODE_C_ADR 0x2028
+
+/* Decoded frame tpe */
+#define S5P_FIMV_DECODE_FRAME_TYPE 0x2020
+#define S5P_FIMV_DECODE_FRAME_MASK 7
+
+#define S5P_FIMV_DECODE_FRAME_SKIPPED 0
+#define S5P_FIMV_DECODE_FRAME_I_FRAME 1
+#define S5P_FIMV_DECODE_FRAME_P_FRAME 2
+#define S5P_FIMV_DECODE_FRAME_B_FRAME 3
+#define S5P_FIMV_DECODE_FRAME_OTHER_FRAME 4
+
+/* Sizes of buffers required for decoding */
+#define S5P_FIMV_DEC_NB_IP_SIZE (32 * 1024)
+#define S5P_FIMV_DEC_VERT_NB_MV_SIZE (16 * 1024)
+#define S5P_FIMV_DEC_NB_DCAC_SIZE (16 * 1024)
+#define S5P_FIMV_DEC_UPNB_MV_SIZE (68 * 1024)
+#define S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE (136 * 1024)
+#define S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE (32 * 1024)
+#define S5P_FIMV_DEC_VC1_BITPLANE_SIZE (2 * 1024)
+#define S5P_FIMV_DEC_STX_PARSER_SIZE (68 * 1024)
+
+#define S5P_FIMV_DEC_BUF_ALIGN (8 * 1024)
+#define S5P_FIMV_ENC_BUF_ALIGN (8 * 1024)
+#define S5P_FIMV_NV12M_HALIGN 16
+#define S5P_FIMV_NV12M_LVALIGN 16
+#define S5P_FIMV_NV12M_CVALIGN 8
+#define S5P_FIMV_NV12MT_HALIGN 128
+#define S5P_FIMV_NV12MT_VALIGN 32
+#define S5P_FIMV_NV12M_SALIGN 2048
+#define S5P_FIMV_NV12MT_SALIGN 8192
+
+/* Sizes of buffers required for encoding */
+#define S5P_FIMV_ENC_UPMV_SIZE 0x10000
+#define S5P_FIMV_ENC_COLFLG_SIZE 0x10000
+#define S5P_FIMV_ENC_INTRAMD_SIZE 0x10000
+#define S5P_FIMV_ENC_INTRAPRED_SIZE 0x4000
+#define S5P_FIMV_ENC_NBORINFO_SIZE 0x10000
+#define S5P_FIMV_ENC_ACDCCOEF_SIZE 0x10000
+
+/* Encoder */
+#define S5P_FIMV_ENC_SI_STRM_SIZE 0x2004 /* stream size */
+#define S5P_FIMV_ENC_SI_PIC_CNT 0x2008 /* picture count */
+#define S5P_FIMV_ENC_SI_WRITE_PTR 0x200c /* write pointer */
+#define S5P_FIMV_ENC_SI_SLICE_TYPE 0x2010 /* slice type(I/P/B/IDR) */
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_NON_CODED 0
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_I 1
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_P 2
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_B 3
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_SKIPPED 4
+#define S5P_FIMV_ENC_SI_SLICE_TYPE_OTHERS 5
+#define S5P_FIMV_ENCODED_Y_ADDR 0x2014 /* the addr of the encoded
+ luma pic */
+#define S5P_FIMV_ENCODED_C_ADDR 0x2018 /* the addr of the encoded
+ chroma pic */
+
+#define S5P_FIMV_ENC_SI_CH0_SB_ADR 0x2044 /* addr of stream buf */
+#define S5P_FIMV_ENC_SI_CH0_SB_SIZE 0x204c /* size of stream buf */
+#define S5P_FIMV_ENC_SI_CH0_CUR_Y_ADR 0x2050 /* current Luma addr */
+#define S5P_FIMV_ENC_SI_CH0_CUR_C_ADR 0x2054 /* current Chroma addr */
+#define S5P_FIMV_ENC_SI_CH0_FRAME_INS 0x2058 /* frame insertion */
+
+#define S5P_FIMV_ENC_SI_CH1_SB_ADR 0x2084 /* addr of stream buf */
+#define S5P_FIMV_ENC_SI_CH1_SB_SIZE 0x208c /* size of stream buf */
+#define S5P_FIMV_ENC_SI_CH1_CUR_Y_ADR 0x2090 /* current Luma addr */
+#define S5P_FIMV_ENC_SI_CH1_CUR_C_ADR 0x2094 /* current Chroma addr */
+#define S5P_FIMV_ENC_SI_CH1_FRAME_INS 0x2098 /* frame insertion */
+
+#define S5P_FIMV_ENC_PIC_TYPE_CTRL 0xc504 /* pic type level control */
+#define S5P_FIMV_ENC_B_RECON_WRITE_ON 0xc508 /* B frame recon write ctrl */
+#define S5P_FIMV_ENC_MSLICE_CTRL 0xc50c /* multi slice control */
+#define S5P_FIMV_ENC_MSLICE_MB 0xc510 /* MB number in the one slice */
+#define S5P_FIMV_ENC_MSLICE_BIT 0xc514 /* bit count for one slice */
+#define S5P_FIMV_ENC_CIR_CTRL 0xc518 /* number of intra refresh MB */
+#define S5P_FIMV_ENC_MAP_FOR_CUR 0xc51c /* linear or tiled mode */
+#define S5P_FIMV_ENC_PADDING_CTRL 0xc520 /* padding control */
+
+#define S5P_FIMV_ENC_RC_CONFIG 0xc5a0 /* RC config */
+#define S5P_FIMV_ENC_RC_BIT_RATE 0xc5a8 /* bit rate */
+#define S5P_FIMV_ENC_RC_QBOUND 0xc5ac /* max/min QP */
+#define S5P_FIMV_ENC_RC_RPARA 0xc5b0 /* rate control reaction coeff */
+#define S5P_FIMV_ENC_RC_MB_CTRL 0xc5b4 /* MB adaptive scaling */
+
+/* Encoder for H264 only */
+#define S5P_FIMV_ENC_H264_ENTROPY_MODE 0xd004 /* CAVLC or CABAC */
+#define S5P_FIMV_ENC_H264_ALPHA_OFF 0xd008 /* loop filter alpha offset */
+#define S5P_FIMV_ENC_H264_BETA_OFF 0xd00c /* loop filter beta offset */
+#define S5P_FIMV_ENC_H264_NUM_OF_REF 0xd010 /* number of reference for P/B */
+#define S5P_FIMV_ENC_H264_TRANS_FLAG 0xd034 /* 8x8 transform flag in PPS &
+ high profile */
+
+#define S5P_FIMV_ENC_RC_FRAME_RATE 0xd0d0 /* frame rate */
+
+/* Encoder for MPEG4 only */
+#define S5P_FIMV_ENC_MPEG4_QUART_PXL 0xe008 /* qpel interpolation ctrl */
+
+/* Additional */
+#define S5P_FIMV_SI_CH0_DPB_CONF_CTRL 0x2068 /* DPB Config Control Register */
+#define S5P_FIMV_SLICE_INT_MASK 1
+#define S5P_FIMV_SLICE_INT_SHIFT 31
+#define S5P_FIMV_DDELAY_ENA_SHIFT 30
+#define S5P_FIMV_DDELAY_VAL_MASK 0xff
+#define S5P_FIMV_DDELAY_VAL_SHIFT 16
+#define S5P_FIMV_DPB_COUNT_MASK 0xffff
+#define S5P_FIMV_DPB_FLUSH_MASK 1
+#define S5P_FIMV_DPB_FLUSH_SHIFT 14
+
+
+#define S5P_FIMV_SI_CH0_RELEASE_BUF 0x2060 /* DPB release buffer register */
+#define S5P_FIMV_SI_CH0_HOST_WR_ADR 0x2064 /* address of shared memory */
+
+/* Codec numbers */
+#define S5P_FIMV_CODEC_NONE -1
+
+#define S5P_FIMV_CODEC_H264_DEC 0
+#define S5P_FIMV_CODEC_VC1_DEC 1
+#define S5P_FIMV_CODEC_MPEG4_DEC 2
+#define S5P_FIMV_CODEC_MPEG2_DEC 3
+#define S5P_FIMV_CODEC_H263_DEC 4
+#define S5P_FIMV_CODEC_VC1RCV_DEC 5
+
+#define S5P_FIMV_CODEC_H264_ENC 16
+#define S5P_FIMV_CODEC_MPEG4_ENC 17
+#define S5P_FIMV_CODEC_H263_ENC 18
+
+/* Channel Control Register */
+#define S5P_FIMV_CH_SEQ_HEADER 1
+#define S5P_FIMV_CH_FRAME_START 2
+#define S5P_FIMV_CH_LAST_FRAME 3
+#define S5P_FIMV_CH_INIT_BUFS 4
+#define S5P_FIMV_CH_FRAME_START_REALLOC 5
+#define S5P_FIMV_CH_MASK 7
+#define S5P_FIMV_CH_SHIFT 16
+
+
+/* Host to RISC command */
+#define S5P_FIMV_H2R_CMD_EMPTY 0
+#define S5P_FIMV_H2R_CMD_OPEN_INSTANCE 1
+#define S5P_FIMV_H2R_CMD_CLOSE_INSTANCE 2
+#define S5P_FIMV_H2R_CMD_SYS_INIT 3
+#define S5P_FIMV_H2R_CMD_FLUSH 4
+#define S5P_FIMV_H2R_CMD_SLEEP 5
+#define S5P_FIMV_H2R_CMD_WAKEUP 6
+
+#define S5P_FIMV_R2H_CMD_EMPTY 0
+#define S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET 1
+#define S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET 2
+#define S5P_FIMV_R2H_CMD_RSV_RET 3
+#define S5P_FIMV_R2H_CMD_SEQ_DONE_RET 4
+#define S5P_FIMV_R2H_CMD_FRAME_DONE_RET 5
+#define S5P_FIMV_R2H_CMD_SLICE_DONE_RET 6
+#define S5P_FIMV_R2H_CMD_ENC_COMPLETE_RET 7
+#define S5P_FIMV_R2H_CMD_SYS_INIT_RET 8
+#define S5P_FIMV_R2H_CMD_FW_STATUS_RET 9
+#define S5P_FIMV_R2H_CMD_SLEEP_RET 10
+#define S5P_FIMV_R2H_CMD_WAKEUP_RET 11
+#define S5P_FIMV_R2H_CMD_FLUSH_RET 12
+#define S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET 15
+#define S5P_FIMV_R2H_CMD_EDFU_INIT_RET 16
+#define S5P_FIMV_R2H_CMD_ERR_RET 32
+
+/* Dummy definition for MFCv6 compatibility */
+#define S5P_FIMV_CODEC_H264_MVC_DEC -1
+#define S5P_FIMV_R2H_CMD_FIELD_DONE_RET -1
+#define S5P_FIMV_MFC_RESET -1
+#define S5P_FIMV_RISC_ON -1
+#define S5P_FIMV_RISC_BASE_ADDRESS -1
+#define S5P_FIMV_CODEC_VP8_DEC -1
+#define S5P_FIMV_REG_CLEAR_BEGIN 0
+#define S5P_FIMV_REG_CLEAR_COUNT 0
+
+/* Error handling defines */
+#define S5P_FIMV_ERR_NO_VALID_SEQ_HDR 67
+#define S5P_FIMV_ERR_INCOMPLETE_FRAME 124
+#define S5P_FIMV_ERR_TIMEOUT 140
+#define S5P_FIMV_ERR_WARNINGS_START 145
+#define S5P_FIMV_ERR_DEC_MASK 0xFFFF
+#define S5P_FIMV_ERR_DEC_SHIFT 0
+#define S5P_FIMV_ERR_DSPL_MASK 0xFFFF0000
+#define S5P_FIMV_ERR_DSPL_SHIFT 16
+
+/* Shared memory registers' offsets */
+
+/* An offset of the start position in the stream when
+ * the start position is not aligned */
+#define S5P_FIMV_SHARED_CROP_INFO_H 0x0020
+#define S5P_FIMV_SHARED_CROP_LEFT_MASK 0xFFFF
+#define S5P_FIMV_SHARED_CROP_LEFT_SHIFT 0
+#define S5P_FIMV_SHARED_CROP_RIGHT_MASK 0xFFFF0000
+#define S5P_FIMV_SHARED_CROP_RIGHT_SHIFT 16
+#define S5P_FIMV_SHARED_CROP_INFO_V 0x0024
+#define S5P_FIMV_SHARED_CROP_TOP_MASK 0xFFFF
+#define S5P_FIMV_SHARED_CROP_TOP_SHIFT 0
+#define S5P_FIMV_SHARED_CROP_BOTTOM_MASK 0xFFFF0000
+#define S5P_FIMV_SHARED_CROP_BOTTOM_SHIFT 16
+#define S5P_FIMV_SHARED_SET_FRAME_TAG 0x0004
+#define S5P_FIMV_SHARED_GET_FRAME_TAG_TOP 0x0008
+#define S5P_FIMV_SHARED_GET_FRAME_TAG_BOT 0x000C
+#define S5P_FIMV_SHARED_START_BYTE_NUM 0x0018
+#define S5P_FIMV_SHARED_RC_VOP_TIMING 0x0030
+#define S5P_FIMV_SHARED_LUMA_DPB_SIZE 0x0064
+#define S5P_FIMV_SHARED_CHROMA_DPB_SIZE 0x0068
+#define S5P_FIMV_SHARED_MV_SIZE 0x006C
+#define S5P_FIMV_SHARED_PIC_TIME_TOP 0x0010
+#define S5P_FIMV_SHARED_PIC_TIME_BOTTOM 0x0014
+#define S5P_FIMV_SHARED_EXT_ENC_CONTROL 0x0028
+#define S5P_FIMV_SHARED_P_B_FRAME_QP 0x0070
+#define S5P_FIMV_SHARED_ASPECT_RATIO_IDC 0x0074
+#define S5P_FIMV_SHARED_EXTENDED_SAR 0x0078
+#define S5P_FIMV_SHARED_H264_I_PERIOD 0x009C
+#define S5P_FIMV_SHARED_RC_CONTROL_CONFIG 0x00A0
+#define S5P_FIMV_SHARED_DISP_FRAME_TYPE_SHIFT 2
+
+/* Offset used by the hardware to store addresses */
+#define MFC_OFFSET_SHIFT 11
+
+#define FIRMWARE_ALIGN (128 * SZ_1K) /* 128KB */
+#define MFC_H264_CTX_BUF_SIZE (600 * SZ_1K) /* 600KB per H264 instance */
+#define MFC_CTX_BUF_SIZE (10 * SZ_1K) /* 10KB per instance */
+#define DESC_BUF_SIZE (128 * SZ_1K) /* 128KB for DESC buffer */
+#define SHARED_BUF_SIZE (8 * SZ_1K) /* 8KB for shared buffer */
+
+#define DEF_CPB_SIZE (256 * SZ_1K) /* 256KB */
+#define MAX_CPB_SIZE (4 * SZ_1M) /* 4MB */
+#define MAX_FW_SIZE (384 * SZ_1K)
+
+#define MFC_VERSION 0x51
+#define MFC_NUM_PORTS 2
+
+#define S5P_FIMV_SHARED_FRAME_PACK_SEI_AVAIL 0x16C
+#define S5P_FIMV_SHARED_FRAME_PACK_ARRGMENT_ID 0x170
+#define S5P_FIMV_SHARED_FRAME_PACK_SEI_INFO 0x174
+#define S5P_FIMV_SHARED_FRAME_PACK_GRID_POS 0x178
+
+/* Values for resolution change in display status */
+#define S5P_FIMV_RES_INCREASE 1
+#define S5P_FIMV_RES_DECREASE 2
+
+#endif /* _REGS_FIMV_H */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c
new file mode 100644
index 0000000000..e30e54935d
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c
@@ -0,0 +1,1705 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung S5P Multi Format Codec v 5.1
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-event.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <media/videobuf2-v4l2.h>
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_ctrl.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_dec.h"
+#include "s5p_mfc_enc.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_iommu.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_pm.h"
+
+#define S5P_MFC_DEC_NAME "s5p-mfc-dec"
+#define S5P_MFC_ENC_NAME "s5p-mfc-enc"
+
+int mfc_debug_level;
+module_param_named(debug, mfc_debug_level, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level - higher value produces more verbose messages");
+
+static char *mfc_mem_size;
+module_param_named(mem, mfc_mem_size, charp, 0644);
+MODULE_PARM_DESC(mem, "Preallocated memory size for the firmware and context buffers");
+
+/* Helper functions for interrupt processing */
+
+/* Remove from hw execution round robin */
+void clear_work_bit(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ spin_lock(&dev->condlock);
+ __clear_bit(ctx->num, &dev->ctx_work_bits);
+ spin_unlock(&dev->condlock);
+}
+
+/* Add to hw execution round robin */
+void set_work_bit(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ spin_lock(&dev->condlock);
+ __set_bit(ctx->num, &dev->ctx_work_bits);
+ spin_unlock(&dev->condlock);
+}
+
+/* Remove from hw execution round robin */
+void clear_work_bit_irqsave(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->condlock, flags);
+ __clear_bit(ctx->num, &dev->ctx_work_bits);
+ spin_unlock_irqrestore(&dev->condlock, flags);
+}
+
+/* Add to hw execution round robin */
+void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->condlock, flags);
+ __set_bit(ctx->num, &dev->ctx_work_bits);
+ spin_unlock_irqrestore(&dev->condlock, flags);
+}
+
+int s5p_mfc_get_new_ctx(struct s5p_mfc_dev *dev)
+{
+ unsigned long flags;
+ int ctx;
+
+ spin_lock_irqsave(&dev->condlock, flags);
+ ctx = dev->curr_ctx;
+ do {
+ ctx = (ctx + 1) % MFC_NUM_CONTEXTS;
+ if (ctx == dev->curr_ctx) {
+ if (!test_bit(ctx, &dev->ctx_work_bits))
+ ctx = -EAGAIN;
+ break;
+ }
+ } while (!test_bit(ctx, &dev->ctx_work_bits));
+ spin_unlock_irqrestore(&dev->condlock, flags);
+
+ return ctx;
+}
+
+/* Wake up context wait_queue */
+static void wake_up_ctx(struct s5p_mfc_ctx *ctx, unsigned int reason,
+ unsigned int err)
+{
+ ctx->int_cond = 1;
+ ctx->int_type = reason;
+ ctx->int_err = err;
+ wake_up(&ctx->queue);
+}
+
+/* Wake up device wait_queue */
+static void wake_up_dev(struct s5p_mfc_dev *dev, unsigned int reason,
+ unsigned int err)
+{
+ dev->int_cond = 1;
+ dev->int_type = reason;
+ dev->int_err = err;
+ wake_up(&dev->queue);
+}
+
+void s5p_mfc_cleanup_queue(struct list_head *lh, struct vb2_queue *vq)
+{
+ struct s5p_mfc_buf *b;
+ int i;
+
+ while (!list_empty(lh)) {
+ b = list_entry(lh->next, struct s5p_mfc_buf, list);
+ for (i = 0; i < b->b->vb2_buf.num_planes; i++)
+ vb2_set_plane_payload(&b->b->vb2_buf, i, 0);
+ vb2_buffer_done(&b->b->vb2_buf, VB2_BUF_STATE_ERROR);
+ list_del(&b->list);
+ }
+}
+
+static void s5p_mfc_watchdog(struct timer_list *t)
+{
+ struct s5p_mfc_dev *dev = from_timer(dev, t, watchdog_timer);
+
+ if (test_bit(0, &dev->hw_lock))
+ atomic_inc(&dev->watchdog_cnt);
+ if (atomic_read(&dev->watchdog_cnt) >= MFC_WATCHDOG_CNT) {
+ /*
+ * This means that hw is busy and no interrupts were
+ * generated by hw for the Nth time of running this
+ * watchdog timer. This usually means a serious hw
+ * error. Now it is time to kill all instances and
+ * reset the MFC.
+ */
+ mfc_err("Time out during waiting for HW\n");
+ schedule_work(&dev->watchdog_work);
+ }
+ dev->watchdog_timer.expires = jiffies +
+ msecs_to_jiffies(MFC_WATCHDOG_INTERVAL);
+ add_timer(&dev->watchdog_timer);
+}
+
+static void s5p_mfc_watchdog_worker(struct work_struct *work)
+{
+ struct s5p_mfc_dev *dev;
+ struct s5p_mfc_ctx *ctx;
+ unsigned long flags;
+ int mutex_locked;
+ int i, ret;
+
+ dev = container_of(work, struct s5p_mfc_dev, watchdog_work);
+
+ mfc_err("Driver timeout error handling\n");
+ /*
+ * Lock the mutex that protects open and release.
+ * This is necessary as they may load and unload firmware.
+ */
+ mutex_locked = mutex_trylock(&dev->mfc_mutex);
+ if (!mutex_locked)
+ mfc_err("Error: some instance may be closing/opening\n");
+ spin_lock_irqsave(&dev->irqlock, flags);
+
+ s5p_mfc_clock_off();
+
+ for (i = 0; i < MFC_NUM_CONTEXTS; i++) {
+ ctx = dev->ctx[i];
+ if (!ctx)
+ continue;
+ ctx->state = MFCINST_ERROR;
+ s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
+ s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
+ clear_work_bit(ctx);
+ wake_up_ctx(ctx, S5P_MFC_R2H_CMD_ERR_RET, 0);
+ }
+ clear_bit(0, &dev->hw_lock);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+
+ /* De-init MFC */
+ s5p_mfc_deinit_hw(dev);
+
+ /*
+ * Double check if there is at least one instance running.
+ * If no instance is in memory than no firmware should be present
+ */
+ if (dev->num_inst > 0) {
+ ret = s5p_mfc_load_firmware(dev);
+ if (ret) {
+ mfc_err("Failed to reload FW\n");
+ goto unlock;
+ }
+ s5p_mfc_clock_on();
+ ret = s5p_mfc_init_hw(dev);
+ s5p_mfc_clock_off();
+ if (ret)
+ mfc_err("Failed to reinit FW\n");
+ }
+unlock:
+ if (mutex_locked)
+ mutex_unlock(&dev->mfc_mutex);
+}
+
+static void s5p_mfc_handle_frame_all_extracted(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_buf *dst_buf;
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ ctx->state = MFCINST_FINISHED;
+ ctx->sequence++;
+ while (!list_empty(&ctx->dst_queue)) {
+ dst_buf = list_entry(ctx->dst_queue.next,
+ struct s5p_mfc_buf, list);
+ mfc_debug(2, "Cleaning up buffer: %d\n",
+ dst_buf->b->vb2_buf.index);
+ vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0, 0);
+ vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1, 0);
+ list_del(&dst_buf->list);
+ dst_buf->flags |= MFC_BUF_FLAG_EOS;
+ ctx->dst_queue_cnt--;
+ dst_buf->b->sequence = (ctx->sequence++);
+
+ if (s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_top, ctx) ==
+ s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_bot, ctx))
+ dst_buf->b->field = V4L2_FIELD_NONE;
+ else
+ dst_buf->b->field = V4L2_FIELD_INTERLACED;
+ dst_buf->b->flags |= V4L2_BUF_FLAG_LAST;
+
+ ctx->dec_dst_flag &= ~(1 << dst_buf->b->vb2_buf.index);
+ vb2_buffer_done(&dst_buf->b->vb2_buf, VB2_BUF_STATE_DONE);
+ }
+}
+
+static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_buf, *src_buf;
+ u32 dec_y_addr;
+ unsigned int frame_type;
+
+ /* Make sure we actually have a new frame before continuing. */
+ frame_type = s5p_mfc_hw_call(dev->mfc_ops, get_dec_frame_type, dev);
+ if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED)
+ return;
+ dec_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dec_y_adr, dev);
+
+ /*
+ * Copy timestamp / timecode from decoded src to dst and set
+ * appropriate flags.
+ */
+ src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ list_for_each_entry(dst_buf, &ctx->dst_queue, list) {
+ u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0);
+
+ if (addr == dec_y_addr) {
+ dst_buf->b->timecode = src_buf->b->timecode;
+ dst_buf->b->vb2_buf.timestamp =
+ src_buf->b->vb2_buf.timestamp;
+ dst_buf->b->flags &=
+ ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ dst_buf->b->flags |=
+ src_buf->b->flags
+ & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
+ switch (frame_type) {
+ case S5P_FIMV_DECODE_FRAME_I_FRAME:
+ dst_buf->b->flags |=
+ V4L2_BUF_FLAG_KEYFRAME;
+ break;
+ case S5P_FIMV_DECODE_FRAME_P_FRAME:
+ dst_buf->b->flags |=
+ V4L2_BUF_FLAG_PFRAME;
+ break;
+ case S5P_FIMV_DECODE_FRAME_B_FRAME:
+ dst_buf->b->flags |=
+ V4L2_BUF_FLAG_BFRAME;
+ break;
+ default:
+ /*
+ * Don't know how to handle
+ * S5P_FIMV_DECODE_FRAME_OTHER_FRAME.
+ */
+ mfc_debug(2, "Unexpected frame type: %d\n",
+ frame_type);
+ }
+ break;
+ }
+ }
+}
+
+static void s5p_mfc_handle_frame_new(struct s5p_mfc_ctx *ctx, unsigned int err)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_buf;
+ u32 dspl_y_addr;
+ unsigned int frame_type;
+
+ dspl_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dspl_y_adr, dev);
+ if (IS_MFCV6_PLUS(dev))
+ frame_type = s5p_mfc_hw_call(dev->mfc_ops,
+ get_disp_frame_type, ctx);
+ else
+ frame_type = s5p_mfc_hw_call(dev->mfc_ops,
+ get_dec_frame_type, dev);
+
+ /* If frame is same as previous then skip and do not dequeue */
+ if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED) {
+ if (!ctx->after_packed_pb)
+ ctx->sequence++;
+ ctx->after_packed_pb = 0;
+ return;
+ }
+ ctx->sequence++;
+ /*
+ * The MFC returns address of the buffer, now we have to
+ * check which vb2_buffer does it correspond to
+ */
+ list_for_each_entry(dst_buf, &ctx->dst_queue, list) {
+ u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0);
+
+ /* Check if this is the buffer we're looking for */
+ if (addr == dspl_y_addr) {
+ list_del(&dst_buf->list);
+ ctx->dst_queue_cnt--;
+ dst_buf->b->sequence = ctx->sequence;
+ if (s5p_mfc_hw_call(dev->mfc_ops,
+ get_pic_type_top, ctx) ==
+ s5p_mfc_hw_call(dev->mfc_ops,
+ get_pic_type_bot, ctx))
+ dst_buf->b->field = V4L2_FIELD_NONE;
+ else
+ dst_buf->b->field =
+ V4L2_FIELD_INTERLACED;
+ vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0,
+ ctx->luma_size);
+ vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1,
+ ctx->chroma_size);
+ clear_bit(dst_buf->b->vb2_buf.index,
+ &ctx->dec_dst_flag);
+
+ vb2_buffer_done(&dst_buf->b->vb2_buf, err ?
+ VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+
+ break;
+ }
+ }
+}
+
+/* Handle frame decoding interrupt */
+static void s5p_mfc_handle_frame(struct s5p_mfc_ctx *ctx,
+ unsigned int reason, unsigned int err)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned int dst_frame_status;
+ unsigned int dec_frame_status;
+ struct s5p_mfc_buf *src_buf;
+ unsigned int res_change;
+
+ dst_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev)
+ & S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK;
+ dec_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dec_status, dev)
+ & S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK;
+ res_change = (s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev)
+ & S5P_FIMV_DEC_STATUS_RESOLUTION_MASK)
+ >> S5P_FIMV_DEC_STATUS_RESOLUTION_SHIFT;
+ mfc_debug(2, "Frame Status: %x\n", dst_frame_status);
+ if (ctx->state == MFCINST_RES_CHANGE_INIT)
+ ctx->state = MFCINST_RES_CHANGE_FLUSH;
+ if (res_change == S5P_FIMV_RES_INCREASE ||
+ res_change == S5P_FIMV_RES_DECREASE) {
+ ctx->state = MFCINST_RES_CHANGE_INIT;
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ wake_up_ctx(ctx, reason, err);
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+ s5p_mfc_clock_off();
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ return;
+ }
+ if (ctx->dpb_flush_flag)
+ ctx->dpb_flush_flag = 0;
+
+ /* All frames remaining in the buffer have been extracted */
+ if (dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_EMPTY) {
+ if (ctx->state == MFCINST_RES_CHANGE_FLUSH) {
+ static const struct v4l2_event ev_src_ch = {
+ .type = V4L2_EVENT_SOURCE_CHANGE,
+ .u.src_change.changes =
+ V4L2_EVENT_SRC_CH_RESOLUTION,
+ };
+
+ s5p_mfc_handle_frame_all_extracted(ctx);
+ ctx->state = MFCINST_RES_CHANGE_END;
+ v4l2_event_queue_fh(&ctx->fh, &ev_src_ch);
+
+ goto leave_handle_frame;
+ } else {
+ s5p_mfc_handle_frame_all_extracted(ctx);
+ }
+ }
+
+ if (dec_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY)
+ s5p_mfc_handle_frame_copy_time(ctx);
+
+ /* A frame has been decoded and is in the buffer */
+ if (dst_frame_status == S5P_FIMV_DEC_STATUS_DISPLAY_ONLY ||
+ dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY) {
+ s5p_mfc_handle_frame_new(ctx, err);
+ } else {
+ mfc_debug(2, "No frame decode\n");
+ }
+ /* Mark source buffer as complete */
+ if (dst_frame_status != S5P_FIMV_DEC_STATUS_DISPLAY_ONLY
+ && !list_empty(&ctx->src_queue)) {
+ src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf,
+ list);
+ ctx->consumed_stream += s5p_mfc_hw_call(dev->mfc_ops,
+ get_consumed_stream, dev);
+ if (ctx->codec_mode != S5P_MFC_CODEC_H264_DEC &&
+ ctx->codec_mode != S5P_MFC_CODEC_VP8_DEC &&
+ ctx->consumed_stream + STUFF_BYTE <
+ src_buf->b->vb2_buf.planes[0].bytesused) {
+ /* Run MFC again on the same buffer */
+ mfc_debug(2, "Running again the same buffer\n");
+ ctx->after_packed_pb = 1;
+ } else {
+ mfc_debug(2, "MFC needs next buffer\n");
+ ctx->consumed_stream = 0;
+ if (src_buf->flags & MFC_BUF_FLAG_EOS)
+ ctx->state = MFCINST_FINISHING;
+ list_del(&src_buf->list);
+ ctx->src_queue_cnt--;
+ if (s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) > 0)
+ vb2_buffer_done(&src_buf->b->vb2_buf,
+ VB2_BUF_STATE_ERROR);
+ else
+ vb2_buffer_done(&src_buf->b->vb2_buf,
+ VB2_BUF_STATE_DONE);
+ }
+ }
+leave_handle_frame:
+ if ((ctx->src_queue_cnt == 0 && ctx->state != MFCINST_FINISHING)
+ || ctx->dst_queue_cnt < ctx->pb_count)
+ clear_work_bit(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ wake_up_ctx(ctx, reason, err);
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+ s5p_mfc_clock_off();
+ /* if suspending, wake up device and do not try_run again*/
+ if (test_bit(0, &dev->enter_suspend))
+ wake_up_dev(dev, reason, err);
+ else
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+}
+
+/* Error handling for interrupt */
+static void s5p_mfc_handle_error(struct s5p_mfc_dev *dev,
+ struct s5p_mfc_ctx *ctx, unsigned int reason, unsigned int err)
+{
+ mfc_err("Interrupt Error: %08x\n", err);
+
+ if (ctx) {
+ /* Error recovery is dependent on the state of context */
+ switch (ctx->state) {
+ case MFCINST_RES_CHANGE_INIT:
+ case MFCINST_RES_CHANGE_FLUSH:
+ case MFCINST_RES_CHANGE_END:
+ case MFCINST_FINISHING:
+ case MFCINST_FINISHED:
+ case MFCINST_RUNNING:
+ /*
+ * It is highly probable that an error occurred
+ * while decoding a frame
+ */
+ clear_work_bit(ctx);
+ ctx->state = MFCINST_ERROR;
+ /* Mark all dst buffers as having an error */
+ s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
+ /* Mark all src buffers as having an error */
+ s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
+ wake_up_ctx(ctx, reason, err);
+ break;
+ default:
+ clear_work_bit(ctx);
+ ctx->state = MFCINST_ERROR;
+ wake_up_ctx(ctx, reason, err);
+ break;
+ }
+ }
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ s5p_mfc_clock_off();
+ wake_up_dev(dev, reason, err);
+}
+
+/* Header parsing interrupt handling */
+static void s5p_mfc_handle_seq_done(struct s5p_mfc_ctx *ctx,
+ unsigned int reason, unsigned int err)
+{
+ struct s5p_mfc_dev *dev;
+
+ if (!ctx)
+ return;
+ dev = ctx->dev;
+ if (ctx->c_ops->post_seq_start) {
+ if (ctx->c_ops->post_seq_start(ctx))
+ mfc_err("post_seq_start() failed\n");
+ } else {
+ ctx->img_width = s5p_mfc_hw_call(dev->mfc_ops, get_img_width,
+ dev);
+ ctx->img_height = s5p_mfc_hw_call(dev->mfc_ops, get_img_height,
+ dev);
+
+ s5p_mfc_hw_call(dev->mfc_ops, dec_calc_dpb_size, ctx);
+
+ ctx->pb_count = s5p_mfc_hw_call(dev->mfc_ops, get_dpb_count,
+ dev);
+ ctx->mv_count = s5p_mfc_hw_call(dev->mfc_ops, get_mv_count,
+ dev);
+ if (FW_HAS_E_MIN_SCRATCH_BUF(dev))
+ ctx->scratch_buf_size = s5p_mfc_hw_call(dev->mfc_ops,
+ get_min_scratch_buf_size, dev);
+ if (ctx->img_width == 0 || ctx->img_height == 0)
+ ctx->state = MFCINST_ERROR;
+ else
+ ctx->state = MFCINST_HEAD_PARSED;
+
+ if ((ctx->codec_mode == S5P_MFC_CODEC_H264_DEC ||
+ ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) &&
+ !list_empty(&ctx->src_queue)) {
+ struct s5p_mfc_buf *src_buf;
+
+ src_buf = list_entry(ctx->src_queue.next,
+ struct s5p_mfc_buf, list);
+ if (s5p_mfc_hw_call(dev->mfc_ops, get_consumed_stream,
+ dev) <
+ src_buf->b->vb2_buf.planes[0].bytesused)
+ ctx->head_processed = 0;
+ else
+ ctx->head_processed = 1;
+ } else {
+ ctx->head_processed = 1;
+ }
+ }
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ clear_work_bit(ctx);
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+ s5p_mfc_clock_off();
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ wake_up_ctx(ctx, reason, err);
+}
+
+/* Header parsing interrupt handling */
+static void s5p_mfc_handle_init_buffers(struct s5p_mfc_ctx *ctx,
+ unsigned int reason, unsigned int err)
+{
+ struct s5p_mfc_buf *src_buf;
+ struct s5p_mfc_dev *dev;
+
+ if (!ctx)
+ return;
+ dev = ctx->dev;
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ ctx->int_type = reason;
+ ctx->int_err = err;
+ ctx->int_cond = 1;
+ clear_work_bit(ctx);
+ if (err == 0) {
+ ctx->state = MFCINST_RUNNING;
+ if (!ctx->dpb_flush_flag && ctx->head_processed) {
+ if (!list_empty(&ctx->src_queue)) {
+ src_buf = list_entry(ctx->src_queue.next,
+ struct s5p_mfc_buf, list);
+ list_del(&src_buf->list);
+ ctx->src_queue_cnt--;
+ vb2_buffer_done(&src_buf->b->vb2_buf,
+ VB2_BUF_STATE_DONE);
+ }
+ } else {
+ ctx->dpb_flush_flag = 0;
+ }
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+
+ s5p_mfc_clock_off();
+
+ wake_up(&ctx->queue);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ } else {
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+
+ s5p_mfc_clock_off();
+
+ wake_up(&ctx->queue);
+ }
+}
+
+static void s5p_mfc_handle_stream_complete(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *mb_entry;
+
+ mfc_debug(2, "Stream completed\n");
+
+ ctx->state = MFCINST_FINISHED;
+
+ if (!list_empty(&ctx->dst_queue)) {
+ mb_entry = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf,
+ list);
+ list_del(&mb_entry->list);
+ ctx->dst_queue_cnt--;
+ vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, 0);
+ vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ clear_work_bit(ctx);
+
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+
+ s5p_mfc_clock_off();
+ wake_up(&ctx->queue);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+}
+
+/* Interrupt processing */
+static irqreturn_t s5p_mfc_irq(int irq, void *priv)
+{
+ struct s5p_mfc_dev *dev = priv;
+ struct s5p_mfc_ctx *ctx;
+ unsigned int reason;
+ unsigned int err;
+
+ mfc_debug_enter();
+ /* Reset the timeout watchdog */
+ atomic_set(&dev->watchdog_cnt, 0);
+ spin_lock(&dev->irqlock);
+ ctx = dev->ctx[dev->curr_ctx];
+ /* Get the reason of interrupt and the error code */
+ reason = s5p_mfc_hw_call(dev->mfc_ops, get_int_reason, dev);
+ err = s5p_mfc_hw_call(dev->mfc_ops, get_int_err, dev);
+ mfc_debug(1, "Int reason: %d (err: %08x)\n", reason, err);
+ switch (reason) {
+ case S5P_MFC_R2H_CMD_ERR_RET:
+ /* An error has occurred */
+ if (ctx->state == MFCINST_RUNNING &&
+ (s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) >=
+ dev->warn_start ||
+ err == S5P_FIMV_ERR_NO_VALID_SEQ_HDR ||
+ err == S5P_FIMV_ERR_INCOMPLETE_FRAME ||
+ err == S5P_FIMV_ERR_TIMEOUT))
+ s5p_mfc_handle_frame(ctx, reason, err);
+ else
+ s5p_mfc_handle_error(dev, ctx, reason, err);
+ clear_bit(0, &dev->enter_suspend);
+ break;
+
+ case S5P_MFC_R2H_CMD_SLICE_DONE_RET:
+ case S5P_MFC_R2H_CMD_FIELD_DONE_RET:
+ case S5P_MFC_R2H_CMD_FRAME_DONE_RET:
+ if (ctx->c_ops->post_frame_start) {
+ if (ctx->c_ops->post_frame_start(ctx))
+ mfc_err("post_frame_start() failed\n");
+
+ if (ctx->state == MFCINST_FINISHING &&
+ list_empty(&ctx->ref_queue)) {
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ s5p_mfc_handle_stream_complete(ctx);
+ break;
+ }
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
+ s5p_mfc_clock_off();
+ wake_up_ctx(ctx, reason, err);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ } else {
+ s5p_mfc_handle_frame(ctx, reason, err);
+ }
+ break;
+
+ case S5P_MFC_R2H_CMD_SEQ_DONE_RET:
+ s5p_mfc_handle_seq_done(ctx, reason, err);
+ break;
+
+ case S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET:
+ ctx->inst_no = s5p_mfc_hw_call(dev->mfc_ops, get_inst_no, dev);
+ ctx->state = MFCINST_GOT_INST;
+ goto irq_cleanup_hw;
+
+ case S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET:
+ ctx->inst_no = MFC_NO_INSTANCE_SET;
+ ctx->state = MFCINST_FREE;
+ goto irq_cleanup_hw;
+
+ case S5P_MFC_R2H_CMD_SYS_INIT_RET:
+ case S5P_MFC_R2H_CMD_FW_STATUS_RET:
+ case S5P_MFC_R2H_CMD_SLEEP_RET:
+ case S5P_MFC_R2H_CMD_WAKEUP_RET:
+ if (ctx)
+ clear_work_bit(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ clear_bit(0, &dev->hw_lock);
+ clear_bit(0, &dev->enter_suspend);
+ wake_up_dev(dev, reason, err);
+ break;
+
+ case S5P_MFC_R2H_CMD_INIT_BUFFERS_RET:
+ s5p_mfc_handle_init_buffers(ctx, reason, err);
+ break;
+
+ case S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET:
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ ctx->int_type = reason;
+ ctx->int_err = err;
+ s5p_mfc_handle_stream_complete(ctx);
+ break;
+
+ case S5P_MFC_R2H_CMD_DPB_FLUSH_RET:
+ ctx->state = MFCINST_RUNNING;
+ goto irq_cleanup_hw;
+
+ default:
+ mfc_debug(2, "Unknown int reason\n");
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ }
+ spin_unlock(&dev->irqlock);
+ mfc_debug_leave();
+ return IRQ_HANDLED;
+irq_cleanup_hw:
+ s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
+ ctx->int_type = reason;
+ ctx->int_err = err;
+ ctx->int_cond = 1;
+ if (test_and_clear_bit(0, &dev->hw_lock) == 0)
+ mfc_err("Failed to unlock hw\n");
+
+ s5p_mfc_clock_off();
+ clear_work_bit(ctx);
+ wake_up(&ctx->queue);
+
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ spin_unlock(&dev->irqlock);
+ mfc_debug(2, "Exit via irq_cleanup_hw\n");
+ return IRQ_HANDLED;
+}
+
+/* Open an MFC node */
+static int s5p_mfc_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_ctx *ctx = NULL;
+ struct vb2_queue *q;
+ int ret = 0;
+
+ mfc_debug_enter();
+ if (mutex_lock_interruptible(&dev->mfc_mutex))
+ return -ERESTARTSYS;
+ dev->num_inst++; /* It is guarded by mfc_mutex in vfd */
+ /* Allocate memory for context */
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+ init_waitqueue_head(&ctx->queue);
+ v4l2_fh_init(&ctx->fh, vdev);
+ file->private_data = &ctx->fh;
+ v4l2_fh_add(&ctx->fh);
+ ctx->dev = dev;
+ INIT_LIST_HEAD(&ctx->src_queue);
+ INIT_LIST_HEAD(&ctx->dst_queue);
+ ctx->src_queue_cnt = 0;
+ ctx->dst_queue_cnt = 0;
+ /* Get context number */
+ ctx->num = 0;
+ while (dev->ctx[ctx->num]) {
+ ctx->num++;
+ if (ctx->num >= MFC_NUM_CONTEXTS) {
+ mfc_debug(2, "Too many open contexts\n");
+ ret = -EBUSY;
+ goto err_no_ctx;
+ }
+ }
+ /* Mark context as idle */
+ clear_work_bit_irqsave(ctx);
+ dev->ctx[ctx->num] = ctx;
+ if (vdev == dev->vfd_dec) {
+ ctx->type = MFCINST_DECODER;
+ ctx->c_ops = get_dec_codec_ops();
+ s5p_mfc_dec_init(ctx);
+ /* Setup ctrl handler */
+ ret = s5p_mfc_dec_ctrls_setup(ctx);
+ if (ret) {
+ mfc_err("Failed to setup mfc controls\n");
+ goto err_ctrls_setup;
+ }
+ } else if (vdev == dev->vfd_enc) {
+ ctx->type = MFCINST_ENCODER;
+ ctx->c_ops = get_enc_codec_ops();
+ /* only for encoder */
+ INIT_LIST_HEAD(&ctx->ref_queue);
+ ctx->ref_queue_cnt = 0;
+ s5p_mfc_enc_init(ctx);
+ /* Setup ctrl handler */
+ ret = s5p_mfc_enc_ctrls_setup(ctx);
+ if (ret) {
+ mfc_err("Failed to setup mfc controls\n");
+ goto err_ctrls_setup;
+ }
+ } else {
+ ret = -ENOENT;
+ goto err_bad_node;
+ }
+ ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+ ctx->inst_no = MFC_NO_INSTANCE_SET;
+ /* Load firmware if this is the first instance */
+ if (dev->num_inst == 1) {
+ dev->watchdog_timer.expires = jiffies +
+ msecs_to_jiffies(MFC_WATCHDOG_INTERVAL);
+ add_timer(&dev->watchdog_timer);
+ ret = s5p_mfc_power_on();
+ if (ret < 0) {
+ mfc_err("power on failed\n");
+ goto err_pwr_enable;
+ }
+ s5p_mfc_clock_on();
+ ret = s5p_mfc_load_firmware(dev);
+ if (ret) {
+ s5p_mfc_clock_off();
+ goto err_load_fw;
+ }
+ /* Init the FW */
+ ret = s5p_mfc_init_hw(dev);
+ s5p_mfc_clock_off();
+ if (ret)
+ goto err_init_hw;
+ }
+ /* Init videobuf2 queue for CAPTURE */
+ q = &ctx->vq_dst;
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->drv_priv = &ctx->fh;
+ q->lock = &dev->mfc_mutex;
+ if (vdev == dev->vfd_dec) {
+ q->io_modes = VB2_MMAP;
+ q->ops = get_dec_queue_ops();
+ } else if (vdev == dev->vfd_enc) {
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->ops = get_enc_queue_ops();
+ } else {
+ ret = -ENOENT;
+ goto err_queue_init;
+ }
+ /*
+ * We'll do mostly sequential access, so sacrifice TLB efficiency for
+ * faster allocation.
+ */
+ q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ ret = vb2_queue_init(q);
+ if (ret) {
+ mfc_err("Failed to initialize videobuf2 queue(capture)\n");
+ goto err_queue_init;
+ }
+ /* Init videobuf2 queue for OUTPUT */
+ q = &ctx->vq_src;
+ q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ q->drv_priv = &ctx->fh;
+ q->lock = &dev->mfc_mutex;
+ if (vdev == dev->vfd_dec) {
+ q->io_modes = VB2_MMAP;
+ q->ops = get_dec_queue_ops();
+ } else if (vdev == dev->vfd_enc) {
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->ops = get_enc_queue_ops();
+ } else {
+ ret = -ENOENT;
+ goto err_queue_init;
+ }
+ /* One way to indicate end-of-stream for MFC is to set the
+ * bytesused == 0. However by default videobuf2 handles bytesused
+ * equal to 0 as a special case and changes its value to the size
+ * of the buffer. Set the allow_zero_bytesused flag so that videobuf2
+ * will keep the value of bytesused intact.
+ */
+ q->allow_zero_bytesused = 1;
+
+ /*
+ * We'll do mostly sequential access, so sacrifice TLB efficiency for
+ * faster allocation.
+ */
+ q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ ret = vb2_queue_init(q);
+ if (ret) {
+ mfc_err("Failed to initialize videobuf2 queue(output)\n");
+ goto err_queue_init;
+ }
+ mutex_unlock(&dev->mfc_mutex);
+ mfc_debug_leave();
+ return ret;
+ /* Deinit when failure occurred */
+err_queue_init:
+ if (dev->num_inst == 1)
+ s5p_mfc_deinit_hw(dev);
+err_init_hw:
+err_load_fw:
+err_pwr_enable:
+ if (dev->num_inst == 1) {
+ if (s5p_mfc_power_off() < 0)
+ mfc_err("power off failed\n");
+ del_timer_sync(&dev->watchdog_timer);
+ }
+err_ctrls_setup:
+ s5p_mfc_dec_ctrls_delete(ctx);
+err_bad_node:
+ dev->ctx[ctx->num] = NULL;
+err_no_ctx:
+ v4l2_fh_del(&ctx->fh);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+err_alloc:
+ dev->num_inst--;
+ mutex_unlock(&dev->mfc_mutex);
+ mfc_debug_leave();
+ return ret;
+}
+
+/* Release MFC context */
+static int s5p_mfc_release(struct file *file)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ /* if dev is null, do cleanup that doesn't need dev */
+ mfc_debug_enter();
+ if (dev)
+ mutex_lock(&dev->mfc_mutex);
+ vb2_queue_release(&ctx->vq_src);
+ vb2_queue_release(&ctx->vq_dst);
+ if (dev) {
+ s5p_mfc_clock_on();
+
+ /* Mark context as idle */
+ clear_work_bit_irqsave(ctx);
+ /*
+ * If instance was initialised and not yet freed,
+ * return instance and free resources
+ */
+ if (ctx->state != MFCINST_FREE && ctx->state != MFCINST_INIT) {
+ mfc_debug(2, "Has to free instance\n");
+ s5p_mfc_close_mfc_inst(dev, ctx);
+ }
+ /* hardware locking scheme */
+ if (dev->curr_ctx == ctx->num)
+ clear_bit(0, &dev->hw_lock);
+ dev->num_inst--;
+ if (dev->num_inst == 0) {
+ mfc_debug(2, "Last instance\n");
+ s5p_mfc_deinit_hw(dev);
+ del_timer_sync(&dev->watchdog_timer);
+ s5p_mfc_clock_off();
+ if (s5p_mfc_power_off() < 0)
+ mfc_err("Power off failed\n");
+ } else {
+ mfc_debug(2, "Shutting down clock\n");
+ s5p_mfc_clock_off();
+ }
+ }
+ if (dev)
+ dev->ctx[ctx->num] = NULL;
+ s5p_mfc_dec_ctrls_delete(ctx);
+ v4l2_fh_del(&ctx->fh);
+ /* vdev is gone if dev is null */
+ if (dev)
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ mfc_debug_leave();
+ if (dev)
+ mutex_unlock(&dev->mfc_mutex);
+
+ return 0;
+}
+
+/* Poll */
+static __poll_t s5p_mfc_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct vb2_queue *src_q, *dst_q;
+ struct vb2_buffer *src_vb = NULL, *dst_vb = NULL;
+ __poll_t rc = 0;
+ unsigned long flags;
+
+ mutex_lock(&dev->mfc_mutex);
+ src_q = &ctx->vq_src;
+ dst_q = &ctx->vq_dst;
+ /*
+ * There has to be at least one buffer queued on each queued_list, which
+ * means either in driver already or waiting for driver to claim it
+ * and start processing.
+ */
+ if ((!vb2_is_streaming(src_q) || list_empty(&src_q->queued_list)) &&
+ (!vb2_is_streaming(dst_q) || list_empty(&dst_q->queued_list))) {
+ rc = EPOLLERR;
+ goto end;
+ }
+ mutex_unlock(&dev->mfc_mutex);
+ poll_wait(file, &ctx->fh.wait, wait);
+ poll_wait(file, &src_q->done_wq, wait);
+ poll_wait(file, &dst_q->done_wq, wait);
+ mutex_lock(&dev->mfc_mutex);
+ if (v4l2_event_pending(&ctx->fh))
+ rc |= EPOLLPRI;
+ spin_lock_irqsave(&src_q->done_lock, flags);
+ if (!list_empty(&src_q->done_list))
+ src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer,
+ done_entry);
+ if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE
+ || src_vb->state == VB2_BUF_STATE_ERROR))
+ rc |= EPOLLOUT | EPOLLWRNORM;
+ spin_unlock_irqrestore(&src_q->done_lock, flags);
+ spin_lock_irqsave(&dst_q->done_lock, flags);
+ if (!list_empty(&dst_q->done_list))
+ dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer,
+ done_entry);
+ if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE
+ || dst_vb->state == VB2_BUF_STATE_ERROR))
+ rc |= EPOLLIN | EPOLLRDNORM;
+ spin_unlock_irqrestore(&dst_q->done_lock, flags);
+end:
+ mutex_unlock(&dev->mfc_mutex);
+ return rc;
+}
+
+/* Mmap */
+static int s5p_mfc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ int ret;
+
+ if (offset < DST_QUEUE_OFF_BASE) {
+ mfc_debug(2, "mmapping source\n");
+ ret = vb2_mmap(&ctx->vq_src, vma);
+ } else { /* capture */
+ mfc_debug(2, "mmapping destination\n");
+ vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT);
+ ret = vb2_mmap(&ctx->vq_dst, vma);
+ }
+ return ret;
+}
+
+/* v4l2 ops */
+static const struct v4l2_file_operations s5p_mfc_fops = {
+ .owner = THIS_MODULE,
+ .open = s5p_mfc_open,
+ .release = s5p_mfc_release,
+ .poll = s5p_mfc_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = s5p_mfc_mmap,
+};
+
+/* DMA memory related helper functions */
+static void s5p_mfc_memdev_release(struct device *dev)
+{
+ of_reserved_mem_device_release(dev);
+}
+
+static struct device *s5p_mfc_alloc_memdev(struct device *dev,
+ const char *name, unsigned int idx)
+{
+ struct device *child;
+ int ret;
+
+ child = devm_kzalloc(dev, sizeof(*child), GFP_KERNEL);
+ if (!child)
+ return NULL;
+
+ device_initialize(child);
+ dev_set_name(child, "%s:%s", dev_name(dev), name);
+ child->parent = dev;
+ child->coherent_dma_mask = dev->coherent_dma_mask;
+ child->dma_mask = dev->dma_mask;
+ child->release = s5p_mfc_memdev_release;
+ child->dma_parms = devm_kzalloc(dev, sizeof(*child->dma_parms),
+ GFP_KERNEL);
+ if (!child->dma_parms)
+ goto err;
+
+ /*
+ * The memdevs are not proper OF platform devices, so in order for them
+ * to be treated as valid DMA masters we need a bit of a hack to force
+ * them to inherit the MFC node's DMA configuration.
+ */
+ of_dma_configure(child, dev->of_node, true);
+
+ if (device_add(child) == 0) {
+ ret = of_reserved_mem_device_init_by_idx(child, dev->of_node,
+ idx);
+ if (ret == 0)
+ return child;
+ device_del(child);
+ }
+err:
+ put_device(child);
+ return NULL;
+}
+
+static int s5p_mfc_configure_2port_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ struct device *dev = &mfc_dev->plat_dev->dev;
+ void *bank2_virt;
+ dma_addr_t bank2_dma_addr;
+ unsigned long align_size = 1 << MFC_BASE_ALIGN_ORDER;
+ int ret;
+
+ /*
+ * Create and initialize virtual devices for accessing
+ * reserved memory regions.
+ */
+ mfc_dev->mem_dev[BANK_L_CTX] = s5p_mfc_alloc_memdev(dev, "left",
+ BANK_L_CTX);
+ if (!mfc_dev->mem_dev[BANK_L_CTX])
+ return -ENODEV;
+ mfc_dev->mem_dev[BANK_R_CTX] = s5p_mfc_alloc_memdev(dev, "right",
+ BANK_R_CTX);
+ if (!mfc_dev->mem_dev[BANK_R_CTX]) {
+ device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
+ return -ENODEV;
+ }
+
+ /* Allocate memory for firmware and initialize both banks addresses */
+ ret = s5p_mfc_alloc_firmware(mfc_dev);
+ if (ret) {
+ device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
+ device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
+ return ret;
+ }
+
+ mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->fw_buf.dma;
+
+ bank2_virt = dma_alloc_coherent(mfc_dev->mem_dev[BANK_R_CTX],
+ align_size, &bank2_dma_addr, GFP_KERNEL);
+ if (!bank2_virt) {
+ s5p_mfc_release_firmware(mfc_dev);
+ device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
+ device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
+ return -ENOMEM;
+ }
+
+ /* Valid buffers passed to MFC encoder with LAST_FRAME command
+ * should not have address of bank2 - MFC will treat it as a null frame.
+ * To avoid such situation we set bank2 address below the pool address.
+ */
+ mfc_dev->dma_base[BANK_R_CTX] = bank2_dma_addr - align_size;
+
+ dma_free_coherent(mfc_dev->mem_dev[BANK_R_CTX], align_size, bank2_virt,
+ bank2_dma_addr);
+
+ vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX],
+ DMA_BIT_MASK(32));
+ vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX],
+ DMA_BIT_MASK(32));
+
+ return 0;
+}
+
+static void s5p_mfc_unconfigure_2port_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
+ device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
+ vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX]);
+ vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX]);
+}
+
+static int s5p_mfc_configure_common_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ struct device *dev = &mfc_dev->plat_dev->dev;
+ unsigned long mem_size = SZ_4M;
+
+ if (IS_ENABLED(CONFIG_DMA_CMA) || exynos_is_iommu_available(dev))
+ mem_size = SZ_8M;
+
+ if (mfc_mem_size)
+ mem_size = memparse(mfc_mem_size, NULL);
+
+ mfc_dev->mem_bitmap = bitmap_zalloc(mem_size >> PAGE_SHIFT, GFP_KERNEL);
+ if (!mfc_dev->mem_bitmap)
+ return -ENOMEM;
+
+ mfc_dev->mem_virt = dma_alloc_coherent(dev, mem_size,
+ &mfc_dev->mem_base, GFP_KERNEL);
+ if (!mfc_dev->mem_virt) {
+ bitmap_free(mfc_dev->mem_bitmap);
+ dev_err(dev, "failed to preallocate %ld MiB for the firmware and context buffers\n",
+ (mem_size / SZ_1M));
+ return -ENOMEM;
+ }
+ mfc_dev->mem_size = mem_size;
+ mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->mem_base;
+ mfc_dev->dma_base[BANK_R_CTX] = mfc_dev->mem_base;
+
+ /*
+ * MFC hardware cannot handle 0 as a base address, so mark first 128K
+ * as used (to keep required base alignment) and adjust base address
+ */
+ if (mfc_dev->mem_base == (dma_addr_t)0) {
+ unsigned int offset = 1 << MFC_BASE_ALIGN_ORDER;
+
+ bitmap_set(mfc_dev->mem_bitmap, 0, offset >> PAGE_SHIFT);
+ mfc_dev->dma_base[BANK_L_CTX] += offset;
+ mfc_dev->dma_base[BANK_R_CTX] += offset;
+ }
+
+ /* Firmware allocation cannot fail in this case */
+ s5p_mfc_alloc_firmware(mfc_dev);
+
+ mfc_dev->mem_dev[BANK_L_CTX] = mfc_dev->mem_dev[BANK_R_CTX] = dev;
+ vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+
+ dev_info(dev, "preallocated %ld MiB buffer for the firmware and context buffers\n",
+ (mem_size / SZ_1M));
+
+ return 0;
+}
+
+static void s5p_mfc_unconfigure_common_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ struct device *dev = &mfc_dev->plat_dev->dev;
+
+ dma_free_coherent(dev, mfc_dev->mem_size, mfc_dev->mem_virt,
+ mfc_dev->mem_base);
+ bitmap_free(mfc_dev->mem_bitmap);
+ vb2_dma_contig_clear_max_seg_size(dev);
+}
+
+static int s5p_mfc_configure_dma_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ struct device *dev = &mfc_dev->plat_dev->dev;
+
+ if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev))
+ return s5p_mfc_configure_common_memory(mfc_dev);
+ else
+ return s5p_mfc_configure_2port_memory(mfc_dev);
+}
+
+static void s5p_mfc_unconfigure_dma_memory(struct s5p_mfc_dev *mfc_dev)
+{
+ struct device *dev = &mfc_dev->plat_dev->dev;
+
+ s5p_mfc_release_firmware(mfc_dev);
+ if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev))
+ s5p_mfc_unconfigure_common_memory(mfc_dev);
+ else
+ s5p_mfc_unconfigure_2port_memory(mfc_dev);
+}
+
+/* MFC probe function */
+static int s5p_mfc_probe(struct platform_device *pdev)
+{
+ struct s5p_mfc_dev *dev;
+ struct video_device *vfd;
+ int ret;
+
+ pr_debug("%s++\n", __func__);
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->irqlock);
+ spin_lock_init(&dev->condlock);
+ dev->plat_dev = pdev;
+ if (!dev->plat_dev) {
+ mfc_err("No platform data specified\n");
+ return -ENODEV;
+ }
+
+ dev->variant = of_device_get_match_data(&pdev->dev);
+ if (!dev->variant) {
+ dev_err(&pdev->dev, "Failed to get device MFC hardware variant information\n");
+ return -ENOENT;
+ }
+
+ dev->regs_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dev->regs_base))
+ return PTR_ERR(dev->regs_base);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ dev->irq = ret;
+ ret = devm_request_irq(&pdev->dev, dev->irq, s5p_mfc_irq,
+ 0, pdev->name, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to install irq (%d)\n", ret);
+ return ret;
+ }
+
+ ret = s5p_mfc_configure_dma_memory(dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to configure DMA memory\n");
+ return ret;
+ }
+
+ ret = s5p_mfc_init_pm(dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to get mfc clock source\n");
+ goto err_dma;
+ }
+
+ /*
+ * Load fails if fs isn't mounted. Try loading anyway.
+ * _open() will load it, it fails now. Ignore failure.
+ */
+ s5p_mfc_load_firmware(dev);
+
+ mutex_init(&dev->mfc_mutex);
+ init_waitqueue_head(&dev->queue);
+ dev->hw_lock = 0;
+ INIT_WORK(&dev->watchdog_work, s5p_mfc_watchdog_worker);
+ atomic_set(&dev->watchdog_cnt, 0);
+ timer_setup(&dev->watchdog_timer, s5p_mfc_watchdog, 0);
+
+ ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+ if (ret)
+ goto err_v4l2_dev_reg;
+
+ /* decoder */
+ vfd = video_device_alloc();
+ if (!vfd) {
+ v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto err_dec_alloc;
+ }
+ vfd->fops = &s5p_mfc_fops;
+ vfd->ioctl_ops = get_dec_v4l2_ioctl_ops();
+ vfd->release = video_device_release;
+ vfd->lock = &dev->mfc_mutex;
+ vfd->v4l2_dev = &dev->v4l2_dev;
+ vfd->vfl_dir = VFL_DIR_M2M;
+ vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+ set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags);
+ snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_DEC_NAME);
+ dev->vfd_dec = vfd;
+ video_set_drvdata(vfd, dev);
+
+ /* encoder */
+ vfd = video_device_alloc();
+ if (!vfd) {
+ v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
+ ret = -ENOMEM;
+ goto err_enc_alloc;
+ }
+ vfd->fops = &s5p_mfc_fops;
+ vfd->ioctl_ops = get_enc_v4l2_ioctl_ops();
+ vfd->release = video_device_release;
+ vfd->lock = &dev->mfc_mutex;
+ vfd->v4l2_dev = &dev->v4l2_dev;
+ vfd->vfl_dir = VFL_DIR_M2M;
+ vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+ snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_ENC_NAME);
+ dev->vfd_enc = vfd;
+ video_set_drvdata(vfd, dev);
+ platform_set_drvdata(pdev, dev);
+
+ /* Initialize HW ops and commands based on MFC version */
+ s5p_mfc_init_hw_ops(dev);
+ s5p_mfc_init_hw_cmds(dev);
+ s5p_mfc_init_regs(dev);
+
+ /* Register decoder and encoder */
+ ret = video_register_device(dev->vfd_dec, VFL_TYPE_VIDEO, 0);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+ goto err_dec_reg;
+ }
+ v4l2_info(&dev->v4l2_dev,
+ "decoder registered as /dev/video%d\n", dev->vfd_dec->num);
+
+ ret = video_register_device(dev->vfd_enc, VFL_TYPE_VIDEO, 0);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+ goto err_enc_reg;
+ }
+ v4l2_info(&dev->v4l2_dev,
+ "encoder registered as /dev/video%d\n", dev->vfd_enc->num);
+
+ pr_debug("%s--\n", __func__);
+ return 0;
+
+/* Deinit MFC if probe had failed */
+err_enc_reg:
+ video_unregister_device(dev->vfd_dec);
+ dev->vfd_dec = NULL;
+err_dec_reg:
+ video_device_release(dev->vfd_enc);
+err_enc_alloc:
+ video_device_release(dev->vfd_dec);
+err_dec_alloc:
+ v4l2_device_unregister(&dev->v4l2_dev);
+err_v4l2_dev_reg:
+ s5p_mfc_final_pm(dev);
+err_dma:
+ s5p_mfc_unconfigure_dma_memory(dev);
+
+ pr_debug("%s-- with error\n", __func__);
+ return ret;
+
+}
+
+/* Remove the driver */
+static void s5p_mfc_remove(struct platform_device *pdev)
+{
+ struct s5p_mfc_dev *dev = platform_get_drvdata(pdev);
+ struct s5p_mfc_ctx *ctx;
+ int i;
+
+ v4l2_info(&dev->v4l2_dev, "Removing %s\n", pdev->name);
+
+ /*
+ * Clear ctx dev pointer to avoid races between s5p_mfc_remove()
+ * and s5p_mfc_release() and s5p_mfc_release() accessing ctx->dev
+ * after s5p_mfc_remove() is run during unbind.
+ */
+ mutex_lock(&dev->mfc_mutex);
+ for (i = 0; i < MFC_NUM_CONTEXTS; i++) {
+ ctx = dev->ctx[i];
+ if (!ctx)
+ continue;
+ /* clear ctx->dev */
+ ctx->dev = NULL;
+ }
+ mutex_unlock(&dev->mfc_mutex);
+
+ del_timer_sync(&dev->watchdog_timer);
+ flush_work(&dev->watchdog_work);
+
+ video_unregister_device(dev->vfd_enc);
+ video_unregister_device(dev->vfd_dec);
+ v4l2_device_unregister(&dev->v4l2_dev);
+ s5p_mfc_unconfigure_dma_memory(dev);
+
+ s5p_mfc_final_pm(dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int s5p_mfc_suspend(struct device *dev)
+{
+ struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev);
+ int ret;
+
+ if (m_dev->num_inst == 0)
+ return 0;
+
+ if (test_and_set_bit(0, &m_dev->enter_suspend) != 0) {
+ mfc_err("Error: going to suspend for a second time\n");
+ return -EIO;
+ }
+
+ /* Check if we're processing then wait if it necessary. */
+ while (test_and_set_bit(0, &m_dev->hw_lock) != 0) {
+ /* Try and lock the HW */
+ /* Wait on the interrupt waitqueue */
+ ret = wait_event_interruptible_timeout(m_dev->queue,
+ m_dev->int_cond, msecs_to_jiffies(MFC_INT_TIMEOUT));
+ if (ret == 0) {
+ mfc_err("Waiting for hardware to finish timed out\n");
+ clear_bit(0, &m_dev->enter_suspend);
+ return -EIO;
+ }
+ }
+
+ ret = s5p_mfc_sleep(m_dev);
+ if (ret) {
+ clear_bit(0, &m_dev->enter_suspend);
+ clear_bit(0, &m_dev->hw_lock);
+ }
+ return ret;
+}
+
+static int s5p_mfc_resume(struct device *dev)
+{
+ struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev);
+
+ if (m_dev->num_inst == 0)
+ return 0;
+ return s5p_mfc_wakeup(m_dev);
+}
+#endif
+
+/* Power management */
+static const struct dev_pm_ops s5p_mfc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(s5p_mfc_suspend, s5p_mfc_resume)
+};
+
+static struct s5p_mfc_buf_size_v5 mfc_buf_size_v5 = {
+ .h264_ctx = MFC_H264_CTX_BUF_SIZE,
+ .non_h264_ctx = MFC_CTX_BUF_SIZE,
+ .dsc = DESC_BUF_SIZE,
+ .shm = SHARED_BUF_SIZE,
+};
+
+static struct s5p_mfc_buf_size buf_size_v5 = {
+ .fw = MAX_FW_SIZE,
+ .cpb = MAX_CPB_SIZE,
+ .priv = &mfc_buf_size_v5,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v5 = {
+ .version = MFC_VERSION,
+ .version_bit = MFC_V5_BIT,
+ .port_num = MFC_NUM_PORTS,
+ .buf_size = &buf_size_v5,
+ .fw_name[0] = "s5p-mfc.fw",
+ .clk_names = {"mfc", "sclk_mfc"},
+ .num_clocks = 2,
+ .use_clock_gating = true,
+};
+
+static struct s5p_mfc_buf_size_v6 mfc_buf_size_v6 = {
+ .dev_ctx = MFC_CTX_BUF_SIZE_V6,
+ .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V6,
+ .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V6,
+ .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V6,
+ .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V6,
+};
+
+static struct s5p_mfc_buf_size buf_size_v6 = {
+ .fw = MAX_FW_SIZE_V6,
+ .cpb = MAX_CPB_SIZE_V6,
+ .priv = &mfc_buf_size_v6,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v6 = {
+ .version = MFC_VERSION_V6,
+ .version_bit = MFC_V6_BIT,
+ .port_num = MFC_NUM_PORTS_V6,
+ .buf_size = &buf_size_v6,
+ .fw_name[0] = "s5p-mfc-v6.fw",
+ /*
+ * v6-v2 firmware contains bug fixes and interface change
+ * for init buffer command
+ */
+ .fw_name[1] = "s5p-mfc-v6-v2.fw",
+ .clk_names = {"mfc"},
+ .num_clocks = 1,
+};
+
+static struct s5p_mfc_buf_size_v6 mfc_buf_size_v7 = {
+ .dev_ctx = MFC_CTX_BUF_SIZE_V7,
+ .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V7,
+ .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V7,
+ .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V7,
+ .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V7,
+};
+
+static struct s5p_mfc_buf_size buf_size_v7 = {
+ .fw = MAX_FW_SIZE_V7,
+ .cpb = MAX_CPB_SIZE_V7,
+ .priv = &mfc_buf_size_v7,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v7 = {
+ .version = MFC_VERSION_V7,
+ .version_bit = MFC_V7_BIT,
+ .port_num = MFC_NUM_PORTS_V7,
+ .buf_size = &buf_size_v7,
+ .fw_name[0] = "s5p-mfc-v7.fw",
+ .clk_names = {"mfc"},
+ .num_clocks = 1,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v7_3250 = {
+ .version = MFC_VERSION_V7,
+ .version_bit = MFC_V7_BIT,
+ .port_num = MFC_NUM_PORTS_V7,
+ .buf_size = &buf_size_v7,
+ .fw_name[0] = "s5p-mfc-v7.fw",
+ .clk_names = {"mfc", "sclk_mfc"},
+ .num_clocks = 2,
+};
+
+static struct s5p_mfc_buf_size_v6 mfc_buf_size_v8 = {
+ .dev_ctx = MFC_CTX_BUF_SIZE_V8,
+ .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V8,
+ .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V8,
+ .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V8,
+ .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V8,
+};
+
+static struct s5p_mfc_buf_size buf_size_v8 = {
+ .fw = MAX_FW_SIZE_V8,
+ .cpb = MAX_CPB_SIZE_V8,
+ .priv = &mfc_buf_size_v8,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v8 = {
+ .version = MFC_VERSION_V8,
+ .version_bit = MFC_V8_BIT,
+ .port_num = MFC_NUM_PORTS_V8,
+ .buf_size = &buf_size_v8,
+ .fw_name[0] = "s5p-mfc-v8.fw",
+ .clk_names = {"mfc"},
+ .num_clocks = 1,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v8_5433 = {
+ .version = MFC_VERSION_V8,
+ .version_bit = MFC_V8_BIT,
+ .port_num = MFC_NUM_PORTS_V8,
+ .buf_size = &buf_size_v8,
+ .fw_name[0] = "s5p-mfc-v8.fw",
+ .clk_names = {"pclk", "aclk", "aclk_xiu"},
+ .num_clocks = 3,
+};
+
+static struct s5p_mfc_buf_size_v6 mfc_buf_size_v10 = {
+ .dev_ctx = MFC_CTX_BUF_SIZE_V10,
+ .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V10,
+ .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V10,
+ .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V10,
+ .hevc_enc_ctx = MFC_HEVC_ENC_CTX_BUF_SIZE_V10,
+ .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V10,
+};
+
+static struct s5p_mfc_buf_size buf_size_v10 = {
+ .fw = MAX_FW_SIZE_V10,
+ .cpb = MAX_CPB_SIZE_V10,
+ .priv = &mfc_buf_size_v10,
+};
+
+static struct s5p_mfc_variant mfc_drvdata_v10 = {
+ .version = MFC_VERSION_V10,
+ .version_bit = MFC_V10_BIT,
+ .port_num = MFC_NUM_PORTS_V10,
+ .buf_size = &buf_size_v10,
+ .fw_name[0] = "s5p-mfc-v10.fw",
+};
+
+static const struct of_device_id exynos_mfc_match[] = {
+ {
+ .compatible = "samsung,mfc-v5",
+ .data = &mfc_drvdata_v5,
+ }, {
+ .compatible = "samsung,mfc-v6",
+ .data = &mfc_drvdata_v6,
+ }, {
+ .compatible = "samsung,mfc-v7",
+ .data = &mfc_drvdata_v7,
+ }, {
+ .compatible = "samsung,exynos3250-mfc",
+ .data = &mfc_drvdata_v7_3250,
+ }, {
+ .compatible = "samsung,mfc-v8",
+ .data = &mfc_drvdata_v8,
+ }, {
+ .compatible = "samsung,exynos5433-mfc",
+ .data = &mfc_drvdata_v8_5433,
+ }, {
+ .compatible = "samsung,mfc-v10",
+ .data = &mfc_drvdata_v10,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_mfc_match);
+
+static struct platform_driver s5p_mfc_driver = {
+ .probe = s5p_mfc_probe,
+ .remove_new = s5p_mfc_remove,
+ .driver = {
+ .name = S5P_MFC_NAME,
+ .pm = &s5p_mfc_pm_ops,
+ .of_match_table = exynos_mfc_match,
+ },
+};
+
+module_platform_driver(s5p_mfc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
+MODULE_DESCRIPTION("Samsung S5P Multi Format Codec V4L2 driver");
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c
new file mode 100644
index 0000000000..774c573dc0
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_cmd_v5.h"
+#include "s5p_mfc_cmd_v6.h"
+
+static struct s5p_mfc_hw_cmds *s5p_mfc_cmds;
+
+void s5p_mfc_init_hw_cmds(struct s5p_mfc_dev *dev)
+{
+ if (IS_MFCV6_PLUS(dev))
+ s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v6();
+ else
+ s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v5();
+
+ dev->mfc_cmds = s5p_mfc_cmds;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h
new file mode 100644
index 0000000000..945d12fdce
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h
+ *
+ * Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_CMD_H_
+#define S5P_MFC_CMD_H_
+
+#include "s5p_mfc_common.h"
+
+#define MAX_H2R_ARG 4
+
+struct s5p_mfc_cmd_args {
+ unsigned int arg[MAX_H2R_ARG];
+};
+
+struct s5p_mfc_hw_cmds {
+ int (*cmd_host2risc)(struct s5p_mfc_dev *dev, int cmd,
+ struct s5p_mfc_cmd_args *args);
+ int (*sys_init_cmd)(struct s5p_mfc_dev *dev);
+ int (*sleep_cmd)(struct s5p_mfc_dev *dev);
+ int (*wakeup_cmd)(struct s5p_mfc_dev *dev);
+ int (*open_inst_cmd)(struct s5p_mfc_ctx *ctx);
+ int (*close_inst_cmd)(struct s5p_mfc_ctx *ctx);
+};
+
+void s5p_mfc_init_hw_cmds(struct s5p_mfc_dev *dev);
+#endif /* S5P_MFC_CMD_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c
new file mode 100644
index 0000000000..327e54e706
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include "regs-mfc.h"
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_cmd_v5.h"
+
+/* This function is used to send a command to the MFC */
+static int s5p_mfc_cmd_host2risc_v5(struct s5p_mfc_dev *dev, int cmd,
+ struct s5p_mfc_cmd_args *args)
+{
+ int cur_cmd;
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT);
+ /* wait until host to risc command register becomes 'H2R_CMD_EMPTY' */
+ do {
+ if (time_after(jiffies, timeout)) {
+ mfc_err("Timeout while waiting for hardware\n");
+ return -EIO;
+ }
+ cur_cmd = mfc_read(dev, S5P_FIMV_HOST2RISC_CMD);
+ } while (cur_cmd != S5P_FIMV_H2R_CMD_EMPTY);
+ mfc_write(dev, args->arg[0], S5P_FIMV_HOST2RISC_ARG1);
+ mfc_write(dev, args->arg[1], S5P_FIMV_HOST2RISC_ARG2);
+ mfc_write(dev, args->arg[2], S5P_FIMV_HOST2RISC_ARG3);
+ mfc_write(dev, args->arg[3], S5P_FIMV_HOST2RISC_ARG4);
+ /* Issue the command */
+ mfc_write(dev, cmd, S5P_FIMV_HOST2RISC_CMD);
+ return 0;
+}
+
+/* Initialize the MFC */
+static int s5p_mfc_sys_init_cmd_v5(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ h2r_args.arg[0] = dev->fw_buf.size;
+ return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_SYS_INIT,
+ &h2r_args);
+}
+
+/* Suspend the MFC hardware */
+static int s5p_mfc_sleep_cmd_v5(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_SLEEP, &h2r_args);
+}
+
+/* Wake up the MFC hardware */
+static int s5p_mfc_wakeup_cmd_v5(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_WAKEUP,
+ &h2r_args);
+}
+
+
+static int s5p_mfc_open_inst_cmd_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_cmd_args h2r_args;
+ int ret;
+
+ /* Preparing decoding - getting instance number */
+ mfc_debug(2, "Getting instance number (codec: %d)\n", ctx->codec_mode);
+ dev->curr_ctx = ctx->num;
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_H264_DEC;
+ break;
+ case S5P_MFC_CODEC_VC1_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_VC1_DEC;
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG4_DEC;
+ break;
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG2_DEC;
+ break;
+ case S5P_MFC_CODEC_H263_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_H263_DEC;
+ break;
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_VC1RCV_DEC;
+ break;
+ case S5P_MFC_CODEC_H264_ENC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_H264_ENC;
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG4_ENC;
+ break;
+ case S5P_MFC_CODEC_H263_ENC:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_H263_ENC;
+ break;
+ default:
+ h2r_args.arg[0] = S5P_FIMV_CODEC_NONE;
+ }
+ h2r_args.arg[1] = 0; /* no crc & no pixelcache */
+ h2r_args.arg[2] = ctx->ctx.ofs;
+ h2r_args.arg[3] = ctx->ctx.size;
+ ret = s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_OPEN_INSTANCE,
+ &h2r_args);
+ if (ret) {
+ mfc_err("Failed to create a new instance\n");
+ ctx->state = MFCINST_ERROR;
+ }
+ return ret;
+}
+
+static int s5p_mfc_close_inst_cmd_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_cmd_args h2r_args;
+ int ret;
+
+ if (ctx->state == MFCINST_FREE) {
+ mfc_err("Instance already returned\n");
+ ctx->state = MFCINST_ERROR;
+ return -EINVAL;
+ }
+ /* Closing decoding instance */
+ mfc_debug(2, "Returning instance number %d\n", ctx->inst_no);
+ dev->curr_ctx = ctx->num;
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ h2r_args.arg[0] = ctx->inst_no;
+ ret = s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_CLOSE_INSTANCE,
+ &h2r_args);
+ if (ret) {
+ mfc_err("Failed to return an instance\n");
+ ctx->state = MFCINST_ERROR;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Initialize cmd function pointers for MFC v5 */
+static struct s5p_mfc_hw_cmds s5p_mfc_cmds_v5 = {
+ .cmd_host2risc = s5p_mfc_cmd_host2risc_v5,
+ .sys_init_cmd = s5p_mfc_sys_init_cmd_v5,
+ .sleep_cmd = s5p_mfc_sleep_cmd_v5,
+ .wakeup_cmd = s5p_mfc_wakeup_cmd_v5,
+ .open_inst_cmd = s5p_mfc_open_inst_cmd_v5,
+ .close_inst_cmd = s5p_mfc_close_inst_cmd_v5,
+};
+
+struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v5(void)
+{
+ return &s5p_mfc_cmds_v5;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h
new file mode 100644
index 0000000000..6eafa514ae
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_CMD_V5_H_
+#define S5P_MFC_CMD_V5_H_
+
+#include "s5p_mfc_common.h"
+
+struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v5(void);
+
+#endif /* S5P_MFC_CMD_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c
new file mode 100644
index 0000000000..f8588e52df
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include "s5p_mfc_common.h"
+
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_cmd_v6.h"
+
+static int s5p_mfc_cmd_host2risc_v6(struct s5p_mfc_dev *dev, int cmd,
+ struct s5p_mfc_cmd_args *args)
+{
+ mfc_debug(2, "Issue the command: %d\n", cmd);
+
+ /* Reset RISC2HOST command */
+ mfc_write(dev, 0x0, S5P_FIMV_RISC2HOST_CMD_V6);
+
+ /* Issue the command */
+ mfc_write(dev, cmd, S5P_FIMV_HOST2RISC_CMD_V6);
+ mfc_write(dev, 0x1, S5P_FIMV_HOST2RISC_INT_V6);
+
+ return 0;
+}
+
+static int s5p_mfc_sys_init_cmd_v6(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+ struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv;
+ int ret;
+
+ ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_dev_context_buffer, dev);
+ if (ret)
+ return ret;
+
+ mfc_write(dev, dev->ctx_buf.dma, S5P_FIMV_CONTEXT_MEM_ADDR_V6);
+ mfc_write(dev, buf_size->dev_ctx, S5P_FIMV_CONTEXT_MEM_SIZE_V6);
+ return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_SYS_INIT_V6,
+ &h2r_args);
+}
+
+static int s5p_mfc_sleep_cmd_v6(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_SLEEP_V6,
+ &h2r_args);
+}
+
+static int s5p_mfc_wakeup_cmd_v6(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_cmd_args h2r_args;
+
+ memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args));
+ return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_WAKEUP_V6,
+ &h2r_args);
+}
+
+/* Open a new instance and get its number */
+static int s5p_mfc_open_inst_cmd_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_cmd_args h2r_args;
+ int codec_type;
+
+ mfc_debug(2, "Requested codec mode: %d\n", ctx->codec_mode);
+ dev->curr_ctx = ctx->num;
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ codec_type = S5P_FIMV_CODEC_H264_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_H264_MVC_DEC:
+ codec_type = S5P_FIMV_CODEC_H264_MVC_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_VC1_DEC:
+ codec_type = S5P_FIMV_CODEC_VC1_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ codec_type = S5P_FIMV_CODEC_MPEG4_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ codec_type = S5P_FIMV_CODEC_MPEG2_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_H263_DEC:
+ codec_type = S5P_FIMV_CODEC_H263_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ codec_type = S5P_FIMV_CODEC_VC1RCV_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_VP8_DEC:
+ codec_type = S5P_FIMV_CODEC_VP8_DEC_V6;
+ break;
+ case S5P_MFC_CODEC_HEVC_DEC:
+ codec_type = S5P_FIMV_CODEC_HEVC_DEC;
+ break;
+ case S5P_MFC_CODEC_VP9_DEC:
+ codec_type = S5P_FIMV_CODEC_VP9_DEC;
+ break;
+ case S5P_MFC_CODEC_H264_ENC:
+ codec_type = S5P_FIMV_CODEC_H264_ENC_V6;
+ break;
+ case S5P_MFC_CODEC_H264_MVC_ENC:
+ codec_type = S5P_FIMV_CODEC_H264_MVC_ENC_V6;
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ codec_type = S5P_FIMV_CODEC_MPEG4_ENC_V6;
+ break;
+ case S5P_MFC_CODEC_H263_ENC:
+ codec_type = S5P_FIMV_CODEC_H263_ENC_V6;
+ break;
+ case S5P_MFC_CODEC_VP8_ENC:
+ codec_type = S5P_FIMV_CODEC_VP8_ENC_V7;
+ break;
+ case S5P_MFC_CODEC_HEVC_ENC:
+ codec_type = S5P_FIMV_CODEC_HEVC_ENC;
+ break;
+ default:
+ codec_type = S5P_FIMV_CODEC_NONE_V6;
+ }
+ mfc_write(dev, codec_type, S5P_FIMV_CODEC_TYPE_V6);
+ mfc_write(dev, ctx->ctx.dma, S5P_FIMV_CONTEXT_MEM_ADDR_V6);
+ mfc_write(dev, ctx->ctx.size, S5P_FIMV_CONTEXT_MEM_SIZE_V6);
+ mfc_write(dev, 0, S5P_FIMV_D_CRC_CTRL_V6); /* no crc */
+
+ return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_OPEN_INSTANCE_V6,
+ &h2r_args);
+}
+
+/* Close instance */
+static int s5p_mfc_close_inst_cmd_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_cmd_args h2r_args;
+ int ret = 0;
+
+ dev->curr_ctx = ctx->num;
+ if (ctx->state != MFCINST_FREE) {
+ mfc_write(dev, ctx->inst_no, S5P_FIMV_INSTANCE_ID_V6);
+ ret = s5p_mfc_cmd_host2risc_v6(dev,
+ S5P_FIMV_H2R_CMD_CLOSE_INSTANCE_V6,
+ &h2r_args);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Initialize cmd function pointers for MFC v6 */
+static struct s5p_mfc_hw_cmds s5p_mfc_cmds_v6 = {
+ .cmd_host2risc = s5p_mfc_cmd_host2risc_v6,
+ .sys_init_cmd = s5p_mfc_sys_init_cmd_v6,
+ .sleep_cmd = s5p_mfc_sleep_cmd_v6,
+ .wakeup_cmd = s5p_mfc_wakeup_cmd_v6,
+ .open_inst_cmd = s5p_mfc_open_inst_cmd_v6,
+ .close_inst_cmd = s5p_mfc_close_inst_cmd_v6,
+};
+
+struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v6(void)
+{
+ return &s5p_mfc_cmds_v6;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h
new file mode 100644
index 0000000000..9dc44460cc
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_CMD_V6_H_
+#define S5P_MFC_CMD_V6_H_
+
+#include "s5p_mfc_common.h"
+
+struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v6(void);
+
+#endif /* S5P_MFC_CMD_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h
new file mode 100644
index 0000000000..5304f42c8c
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h
@@ -0,0 +1,792 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Samsung S5P Multi Format Codec v 5.0
+ *
+ * This file contains definitions of enums and structs used by the codec
+ * driver.
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#ifndef S5P_MFC_COMMON_H_
+#define S5P_MFC_COMMON_H_
+
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include "regs-mfc.h"
+#include "regs-mfc-v10.h"
+
+#define S5P_MFC_NAME "s5p-mfc"
+
+/* Definitions related to MFC memory */
+
+/* Offset base used to differentiate between CAPTURE and OUTPUT
+* while mmaping */
+#define DST_QUEUE_OFF_BASE (1 << 30)
+
+#define BANK_L_CTX 0
+#define BANK_R_CTX 1
+#define BANK_CTX_NUM 2
+
+#define MFC_BANK1_ALIGN_ORDER 13
+#define MFC_BANK2_ALIGN_ORDER 13
+#define MFC_BASE_ALIGN_ORDER 17
+
+#define MFC_FW_MAX_VERSIONS 2
+
+#include <media/videobuf2-dma-contig.h>
+
+/* MFC definitions */
+#define MFC_MAX_EXTRA_DPB 5
+#define MFC_MAX_BUFFERS 32
+#define MFC_NUM_CONTEXTS 4
+/* Interrupt timeout */
+#define MFC_INT_TIMEOUT 2000
+/* Busy wait timeout */
+#define MFC_BW_TIMEOUT 500
+/* Watchdog interval */
+#define MFC_WATCHDOG_INTERVAL 1000
+/* After how many executions watchdog should assume lock up */
+#define MFC_WATCHDOG_CNT 10
+#define MFC_NO_INSTANCE_SET -1
+#define MFC_ENC_CAP_PLANE_COUNT 1
+#define MFC_ENC_OUT_PLANE_COUNT 2
+#define STUFF_BYTE 4
+#define MFC_MAX_CTRLS 128
+
+#define S5P_MFC_CODEC_NONE -1
+#define S5P_MFC_CODEC_H264_DEC 0
+#define S5P_MFC_CODEC_H264_MVC_DEC 1
+#define S5P_MFC_CODEC_VC1_DEC 2
+#define S5P_MFC_CODEC_MPEG4_DEC 3
+#define S5P_MFC_CODEC_MPEG2_DEC 4
+#define S5P_MFC_CODEC_H263_DEC 5
+#define S5P_MFC_CODEC_VC1RCV_DEC 6
+#define S5P_MFC_CODEC_VP8_DEC 7
+#define S5P_MFC_CODEC_HEVC_DEC 17
+#define S5P_MFC_CODEC_VP9_DEC 18
+
+#define S5P_MFC_CODEC_H264_ENC 20
+#define S5P_MFC_CODEC_H264_MVC_ENC 21
+#define S5P_MFC_CODEC_MPEG4_ENC 22
+#define S5P_MFC_CODEC_H263_ENC 23
+#define S5P_MFC_CODEC_VP8_ENC 24
+#define S5P_MFC_CODEC_HEVC_ENC 26
+
+#define S5P_MFC_R2H_CMD_EMPTY 0
+#define S5P_MFC_R2H_CMD_SYS_INIT_RET 1
+#define S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET 2
+#define S5P_MFC_R2H_CMD_SEQ_DONE_RET 3
+#define S5P_MFC_R2H_CMD_INIT_BUFFERS_RET 4
+#define S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET 6
+#define S5P_MFC_R2H_CMD_SLEEP_RET 7
+#define S5P_MFC_R2H_CMD_WAKEUP_RET 8
+#define S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET 9
+#define S5P_MFC_R2H_CMD_DPB_FLUSH_RET 10
+#define S5P_MFC_R2H_CMD_NAL_ABORT_RET 11
+#define S5P_MFC_R2H_CMD_FW_STATUS_RET 12
+#define S5P_MFC_R2H_CMD_FRAME_DONE_RET 13
+#define S5P_MFC_R2H_CMD_FIELD_DONE_RET 14
+#define S5P_MFC_R2H_CMD_SLICE_DONE_RET 15
+#define S5P_MFC_R2H_CMD_ENC_BUFFER_FUL_RET 16
+#define S5P_MFC_R2H_CMD_ERR_RET 32
+
+#define MFC_MAX_CLOCKS 4
+
+#define mfc_read(dev, offset) readl(dev->regs_base + (offset))
+#define mfc_write(dev, data, offset) writel((data), dev->regs_base + \
+ (offset))
+
+/*
+ * enum s5p_mfc_fmt_type - type of the pixelformat
+ */
+enum s5p_mfc_fmt_type {
+ MFC_FMT_DEC,
+ MFC_FMT_ENC,
+ MFC_FMT_RAW,
+};
+
+/*
+ * enum s5p_mfc_inst_type - The type of an MFC instance.
+ */
+enum s5p_mfc_inst_type {
+ MFCINST_INVALID,
+ MFCINST_DECODER,
+ MFCINST_ENCODER,
+};
+
+/*
+ * enum s5p_mfc_inst_state - The state of an MFC instance.
+ */
+enum s5p_mfc_inst_state {
+ MFCINST_FREE = 0,
+ MFCINST_INIT = 100,
+ MFCINST_GOT_INST,
+ MFCINST_HEAD_PARSED,
+ MFCINST_HEAD_PRODUCED,
+ MFCINST_BUFS_SET,
+ MFCINST_RUNNING,
+ MFCINST_FINISHING,
+ MFCINST_FINISHED,
+ MFCINST_RETURN_INST,
+ MFCINST_ERROR,
+ MFCINST_ABORT,
+ MFCINST_FLUSH,
+ MFCINST_RES_CHANGE_INIT,
+ MFCINST_RES_CHANGE_FLUSH,
+ MFCINST_RES_CHANGE_END,
+};
+
+/*
+ * enum s5p_mfc_queue_state - The state of buffer queue.
+ */
+enum s5p_mfc_queue_state {
+ QUEUE_FREE,
+ QUEUE_BUFS_REQUESTED,
+ QUEUE_BUFS_QUERIED,
+ QUEUE_BUFS_MMAPED,
+};
+
+/*
+ * enum s5p_mfc_decode_arg - type of frame decoding
+ */
+enum s5p_mfc_decode_arg {
+ MFC_DEC_FRAME,
+ MFC_DEC_LAST_FRAME,
+ MFC_DEC_RES_CHANGE,
+};
+
+enum s5p_mfc_fw_ver {
+ MFC_FW_V1,
+ MFC_FW_V2,
+};
+
+#define MFC_BUF_FLAG_USED (1 << 0)
+#define MFC_BUF_FLAG_EOS (1 << 1)
+
+struct s5p_mfc_ctx;
+
+/*
+ * struct s5p_mfc_buf - MFC buffer
+ */
+struct s5p_mfc_buf {
+ struct vb2_v4l2_buffer *b;
+ struct list_head list;
+ union {
+ struct {
+ size_t luma;
+ size_t chroma;
+ } raw;
+ size_t stream;
+ } cookie;
+ int flags;
+};
+
+/*
+ * struct s5p_mfc_pm - power management data structure
+ */
+struct s5p_mfc_pm {
+ struct clk *clock_gate;
+ const char * const *clk_names;
+ struct clk *clocks[MFC_MAX_CLOCKS];
+ int num_clocks;
+ bool use_clock_gating;
+
+ struct device *device;
+};
+
+struct s5p_mfc_buf_size_v5 {
+ unsigned int h264_ctx;
+ unsigned int non_h264_ctx;
+ unsigned int dsc;
+ unsigned int shm;
+};
+
+struct s5p_mfc_buf_size_v6 {
+ unsigned int dev_ctx;
+ unsigned int h264_dec_ctx;
+ unsigned int other_dec_ctx;
+ unsigned int h264_enc_ctx;
+ unsigned int hevc_enc_ctx;
+ unsigned int other_enc_ctx;
+};
+
+struct s5p_mfc_buf_size {
+ unsigned int fw;
+ unsigned int cpb;
+ void *priv;
+};
+
+struct s5p_mfc_variant {
+ unsigned int version;
+ unsigned int port_num;
+ u32 version_bit;
+ struct s5p_mfc_buf_size *buf_size;
+ char *fw_name[MFC_FW_MAX_VERSIONS];
+ const char *clk_names[MFC_MAX_CLOCKS];
+ int num_clocks;
+ bool use_clock_gating;
+};
+
+/**
+ * struct s5p_mfc_priv_buf - represents internal used buffer
+ * @ofs: offset of each buffer, will be used for MFC
+ * @virt: kernel virtual address, only valid when the
+ * buffer accessed by driver
+ * @dma: DMA address, only valid when kernel DMA API used
+ * @size: size of the buffer
+ * @ctx: memory context (bank) used for this allocation
+ */
+struct s5p_mfc_priv_buf {
+ unsigned long ofs;
+ void *virt;
+ dma_addr_t dma;
+ size_t size;
+ unsigned int ctx;
+};
+
+/**
+ * struct s5p_mfc_dev - The struct containing driver internal parameters.
+ *
+ * @v4l2_dev: v4l2_device
+ * @vfd_dec: video device for decoding
+ * @vfd_enc: video device for encoding
+ * @plat_dev: platform device
+ * @mem_dev: child devices of the memory banks
+ * @regs_base: base address of the MFC hw registers
+ * @irq: irq resource
+ * @dec_ctrl_handler: control framework handler for decoding
+ * @enc_ctrl_handler: control framework handler for encoding
+ * @pm: power management control
+ * @variant: MFC hardware variant information
+ * @num_inst: counter of active MFC instances
+ * @irqlock: lock for operations on videobuf2 queues
+ * @condlock: lock for changing/checking if a context is ready to be
+ * processed
+ * @mfc_mutex: lock for video_device
+ * @int_cond: variable used by the waitqueue
+ * @int_type: type of last interrupt
+ * @int_err: error number for last interrupt
+ * @queue: waitqueue for waiting for completion of device commands
+ * @fw_buf: the firmware buffer data structure
+ * @mem_size: size of the firmware operation memory
+ * @mem_base: base DMA address of the firmware operation memory
+ * @mem_bitmap: bitmap for managing MFC internal buffer allocations
+ * @mem_virt: virtual address of the firmware operation memory
+ * @dma_base: address of the beginning of memory banks
+ * @hw_lock: used for hardware locking
+ * @ctx: array of driver contexts
+ * @curr_ctx: number of the currently running context
+ * @ctx_work_bits: used to mark which contexts are waiting for hardware
+ * @watchdog_cnt: counter for the watchdog
+ * @watchdog_timer: timer for the watchdog
+ * @watchdog_workqueue: workqueue for the watchdog
+ * @watchdog_work: worker for the watchdog
+ * @enter_suspend: flag set when entering suspend
+ * @ctx_buf: common context memory (MFCv6)
+ * @warn_start: hardware error code from which warnings start
+ * @mfc_ops: ops structure holding HW operation function pointers
+ * @mfc_cmds: cmd structure holding HW commands function pointers
+ * @mfc_regs: structure holding MFC registers
+ * @fw_ver: loaded firmware sub-version
+ * @fw_get_done: flag set when request_firmware() is complete and
+ * copied into fw_buf
+ * @risc_on: flag indicates RISC is on or off
+ *
+ */
+struct s5p_mfc_dev {
+ struct v4l2_device v4l2_dev;
+ struct video_device *vfd_dec;
+ struct video_device *vfd_enc;
+ struct platform_device *plat_dev;
+ struct device *mem_dev[BANK_CTX_NUM];
+ void __iomem *regs_base;
+ int irq;
+ struct v4l2_ctrl_handler dec_ctrl_handler;
+ struct v4l2_ctrl_handler enc_ctrl_handler;
+ struct s5p_mfc_pm pm;
+ const struct s5p_mfc_variant *variant;
+ int num_inst;
+ spinlock_t irqlock; /* lock when operating on context */
+ spinlock_t condlock; /* lock when changing/checking if a context is
+ ready to be processed */
+ struct mutex mfc_mutex; /* video_device lock */
+ int int_cond;
+ int int_type;
+ unsigned int int_err;
+ wait_queue_head_t queue;
+ struct s5p_mfc_priv_buf fw_buf;
+ size_t mem_size;
+ dma_addr_t mem_base;
+ unsigned long *mem_bitmap;
+ void *mem_virt;
+ dma_addr_t dma_base[BANK_CTX_NUM];
+ unsigned long hw_lock;
+ struct s5p_mfc_ctx *ctx[MFC_NUM_CONTEXTS];
+ int curr_ctx;
+ unsigned long ctx_work_bits;
+ atomic_t watchdog_cnt;
+ struct timer_list watchdog_timer;
+ struct workqueue_struct *watchdog_workqueue;
+ struct work_struct watchdog_work;
+ unsigned long enter_suspend;
+
+ struct s5p_mfc_priv_buf ctx_buf;
+ int warn_start;
+ struct s5p_mfc_hw_ops *mfc_ops;
+ struct s5p_mfc_hw_cmds *mfc_cmds;
+ const struct s5p_mfc_regs *mfc_regs;
+ enum s5p_mfc_fw_ver fw_ver;
+ bool fw_get_done;
+ bool risc_on; /* indicates if RISC is on or off */
+};
+
+/*
+ * struct s5p_mfc_h264_enc_params - encoding parameters for h264
+ */
+struct s5p_mfc_h264_enc_params {
+ enum v4l2_mpeg_video_h264_profile profile;
+ enum v4l2_mpeg_video_h264_loop_filter_mode loop_filter_mode;
+ s8 loop_filter_alpha;
+ s8 loop_filter_beta;
+ enum v4l2_mpeg_video_h264_entropy_mode entropy_mode;
+ u8 max_ref_pic;
+ u8 num_ref_pic_4p;
+ int _8x8_transform;
+ int rc_mb_dark;
+ int rc_mb_smooth;
+ int rc_mb_static;
+ int rc_mb_activity;
+ int vui_sar;
+ u8 vui_sar_idc;
+ u16 vui_ext_sar_width;
+ u16 vui_ext_sar_height;
+ int open_gop;
+ u16 open_gop_size;
+ u8 rc_frame_qp;
+ u8 rc_min_qp;
+ u8 rc_max_qp;
+ u8 rc_p_frame_qp;
+ u8 rc_b_frame_qp;
+ enum v4l2_mpeg_video_h264_level level_v4l2;
+ int level;
+ u16 cpb_size;
+ int interlace;
+ u8 hier_qp;
+ u8 hier_qp_type;
+ u8 hier_qp_layer;
+ u8 hier_qp_layer_qp[7];
+ u8 sei_frame_packing;
+ u8 sei_fp_curr_frame_0;
+ u8 sei_fp_arrangement_type;
+
+ u8 fmo;
+ u8 fmo_map_type;
+ u8 fmo_slice_grp;
+ u8 fmo_chg_dir;
+ u32 fmo_chg_rate;
+ u32 fmo_run_len[4];
+ u8 aso;
+ u32 aso_slice_order[8];
+};
+
+/*
+ * struct s5p_mfc_mpeg4_enc_params - encoding parameters for h263 and mpeg4
+ */
+struct s5p_mfc_mpeg4_enc_params {
+ /* MPEG4 Only */
+ enum v4l2_mpeg_video_mpeg4_profile profile;
+ int quarter_pixel;
+ /* Common for MPEG4, H263 */
+ u16 vop_time_res;
+ u16 vop_frm_delta;
+ u8 rc_frame_qp;
+ u8 rc_min_qp;
+ u8 rc_max_qp;
+ u8 rc_p_frame_qp;
+ u8 rc_b_frame_qp;
+ enum v4l2_mpeg_video_mpeg4_level level_v4l2;
+ int level;
+};
+
+/*
+ * struct s5p_mfc_vp8_enc_params - encoding parameters for vp8
+ */
+struct s5p_mfc_vp8_enc_params {
+ u8 imd_4x4;
+ enum v4l2_vp8_num_partitions num_partitions;
+ enum v4l2_vp8_num_ref_frames num_ref;
+ u8 filter_level;
+ u8 filter_sharpness;
+ u32 golden_frame_ref_period;
+ enum v4l2_vp8_golden_frame_sel golden_frame_sel;
+ u8 hier_layer;
+ u8 hier_layer_qp[3];
+ u8 rc_min_qp;
+ u8 rc_max_qp;
+ u8 rc_frame_qp;
+ u8 rc_p_frame_qp;
+ u8 profile;
+};
+
+struct s5p_mfc_hevc_enc_params {
+ enum v4l2_mpeg_video_hevc_profile profile;
+ int level;
+ enum v4l2_mpeg_video_h264_level level_v4l2;
+ u8 tier;
+ u32 rc_framerate;
+ u8 rc_min_qp;
+ u8 rc_max_qp;
+ u8 rc_lcu_dark;
+ u8 rc_lcu_smooth;
+ u8 rc_lcu_static;
+ u8 rc_lcu_activity;
+ u8 rc_frame_qp;
+ u8 rc_p_frame_qp;
+ u8 rc_b_frame_qp;
+ u8 max_partition_depth;
+ u8 num_refs_for_p;
+ u8 refreshtype;
+ u16 refreshperiod;
+ s32 lf_beta_offset_div2;
+ s32 lf_tc_offset_div2;
+ u8 loopfilter;
+ u8 loopfilter_disable;
+ u8 loopfilter_across;
+ u8 nal_control_length_filed;
+ u8 nal_control_user_ref;
+ u8 nal_control_store_ref;
+ u8 const_intra_period_enable;
+ u8 lossless_cu_enable;
+ u8 wavefront_enable;
+ u8 enable_ltr;
+ u8 hier_qp_enable;
+ enum v4l2_mpeg_video_hevc_hier_coding_type hier_qp_type;
+ u8 num_hier_layer;
+ u8 hier_qp_layer[7];
+ u32 hier_bit_layer[7];
+ u8 sign_data_hiding;
+ u8 general_pb_enable;
+ u8 temporal_id_enable;
+ u8 strong_intra_smooth;
+ u8 intra_pu_split_disable;
+ u8 tmv_prediction_disable;
+ u8 max_num_merge_mv;
+ u8 eco_mode_enable;
+ u8 encoding_nostartcode_enable;
+ u8 size_of_length_field;
+ u8 prepend_sps_pps_to_idr;
+};
+
+/*
+ * struct s5p_mfc_enc_params - general encoding parameters
+ */
+struct s5p_mfc_enc_params {
+ u16 width;
+ u16 height;
+ u32 mv_h_range;
+ u32 mv_v_range;
+
+ u16 gop_size;
+ enum v4l2_mpeg_video_multi_slice_mode slice_mode;
+ u16 slice_mb;
+ u32 slice_bit;
+ u16 intra_refresh_mb;
+ int pad;
+ u8 pad_luma;
+ u8 pad_cb;
+ u8 pad_cr;
+ int rc_frame;
+ int rc_mb;
+ u32 rc_bitrate;
+ u16 rc_reaction_coeff;
+ u16 vbv_size;
+ u32 vbv_delay;
+
+ enum v4l2_mpeg_video_header_mode seq_hdr_mode;
+ enum v4l2_mpeg_mfc51_video_frame_skip_mode frame_skip_mode;
+ int fixed_target_bit;
+
+ u8 num_b_frame;
+ u32 rc_framerate_num;
+ u32 rc_framerate_denom;
+
+ struct {
+ struct s5p_mfc_h264_enc_params h264;
+ struct s5p_mfc_mpeg4_enc_params mpeg4;
+ struct s5p_mfc_vp8_enc_params vp8;
+ struct s5p_mfc_hevc_enc_params hevc;
+ } codec;
+
+};
+
+/*
+ * struct s5p_mfc_codec_ops - codec ops, used by encoding
+ */
+struct s5p_mfc_codec_ops {
+ /* initialization routines */
+ int (*pre_seq_start) (struct s5p_mfc_ctx *ctx);
+ int (*post_seq_start) (struct s5p_mfc_ctx *ctx);
+ /* execution routines */
+ int (*pre_frame_start) (struct s5p_mfc_ctx *ctx);
+ int (*post_frame_start) (struct s5p_mfc_ctx *ctx);
+};
+
+#define call_cop(c, op, args...) \
+ (((c)->c_ops->op) ? \
+ ((c)->c_ops->op(args)) : 0)
+
+/**
+ * struct s5p_mfc_ctx - This struct contains the instance context
+ *
+ * @dev: pointer to the s5p_mfc_dev of the device
+ * @fh: struct v4l2_fh
+ * @num: number of the context that this structure describes
+ * @int_cond: variable used by the waitqueue
+ * @int_type: type of the last interrupt
+ * @int_err: error number received from MFC hw in the interrupt
+ * @queue: waitqueue that can be used to wait for this context to
+ * finish
+ * @src_fmt: source pixelformat information
+ * @dst_fmt: destination pixelformat information
+ * @vq_src: vb2 queue for source buffers
+ * @vq_dst: vb2 queue for destination buffers
+ * @src_queue: driver internal queue for source buffers
+ * @dst_queue: driver internal queue for destination buffers
+ * @src_queue_cnt: number of buffers queued on the source internal queue
+ * @dst_queue_cnt: number of buffers queued on the dest internal queue
+ * @type: type of the instance - decoder or encoder
+ * @state: state of the context
+ * @inst_no: number of hw instance associated with the context
+ * @img_width: width of the image that is decoded or encoded
+ * @img_height: height of the image that is decoded or encoded
+ * @buf_width: width of the buffer for processed image
+ * @buf_height: height of the buffer for processed image
+ * @luma_size: size of a luma plane
+ * @chroma_size: size of a chroma plane
+ * @mv_size: size of a motion vectors buffer
+ * @consumed_stream: number of bytes that have been used so far from the
+ * decoding buffer
+ * @dpb_flush_flag: flag used to indicate that a DPB buffers are being
+ * flushed
+ * @head_processed: flag mentioning whether the header data is processed
+ * completely or not
+ * @bank1: handle to memory allocated for temporary buffers from
+ * memory bank 1
+ * @bank2: handle to memory allocated for temporary buffers from
+ * memory bank 2
+ * @capture_state: state of the capture buffers queue
+ * @output_state: state of the output buffers queue
+ * @src_bufs: information on allocated source buffers
+ * @src_bufs_cnt: number of allocated source buffers
+ * @dst_bufs: information on allocated destination buffers
+ * @dst_bufs_cnt: number of allocated destination buffers
+ * @sequence: counter for the sequence number for v4l2
+ * @dec_dst_flag: flags for buffers queued in the hardware
+ * @dec_src_buf_size: size of the buffer for source buffers in decoding
+ * @codec_mode: number of codec mode used by MFC hw
+ * @slice_interface: slice interface flag
+ * @loop_filter_mpeg4: loop filter for MPEG4 flag
+ * @display_delay: value of the display delay for H264
+ * @display_delay_enable: display delay for H264 enable flag
+ * @after_packed_pb: flag used to track buffer when stream is in
+ * Packed PB format
+ * @sei_fp_parse: enable/disable parsing of frame packing SEI information
+ * @pb_count: count of the DPB buffers required by MFC hw
+ * @total_dpb_count: count of DPB buffers with additional buffers
+ * requested by the application
+ * @ctx: context buffer information
+ * @dsc: descriptor buffer information
+ * @shm: shared memory buffer information
+ * @mv_count: number of MV buffers allocated for decoding
+ * @enc_params: encoding parameters for MFC
+ * @enc_dst_buf_size: size of the buffers for encoder output
+ * @luma_dpb_size: dpb buffer size for luma
+ * @chroma_dpb_size: dpb buffer size for chroma
+ * @me_buffer_size: size of the motion estimation buffer
+ * @tmv_buffer_size: size of temporal predictor motion vector buffer
+ * @frame_type: used to force the type of the next encoded frame
+ * @ref_queue: list of the reference buffers for encoding
+ * @force_frame_type: encoder's frame type forcing control
+ * @ref_queue_cnt: number of the buffers in the reference list
+ * @slice_size: slice size
+ * @slice_mode: mode of dividing frames into slices
+ * @c_ops: ops for encoding
+ * @ctrls: array of controls, used when adding controls to the
+ * v4l2 control framework
+ * @ctrl_handler: handler for v4l2 framework
+ * @scratch_buf_size: scratch buffer size
+ */
+struct s5p_mfc_ctx {
+ struct s5p_mfc_dev *dev;
+ struct v4l2_fh fh;
+
+ int num;
+
+ int int_cond;
+ int int_type;
+ unsigned int int_err;
+ wait_queue_head_t queue;
+
+ struct s5p_mfc_fmt *src_fmt;
+ struct s5p_mfc_fmt *dst_fmt;
+
+ struct vb2_queue vq_src;
+ struct vb2_queue vq_dst;
+
+ struct list_head src_queue;
+ struct list_head dst_queue;
+
+ unsigned int src_queue_cnt;
+ unsigned int dst_queue_cnt;
+
+ enum s5p_mfc_inst_type type;
+ enum s5p_mfc_inst_state state;
+ int inst_no;
+
+ /* Image parameters */
+ int img_width;
+ int img_height;
+ int buf_width;
+ int buf_height;
+
+ int luma_size;
+ int chroma_size;
+ int mv_size;
+
+ unsigned long consumed_stream;
+
+ unsigned int dpb_flush_flag;
+ unsigned int head_processed;
+
+ struct s5p_mfc_priv_buf bank1;
+ struct s5p_mfc_priv_buf bank2;
+
+ enum s5p_mfc_queue_state capture_state;
+ enum s5p_mfc_queue_state output_state;
+
+ struct s5p_mfc_buf src_bufs[MFC_MAX_BUFFERS];
+ int src_bufs_cnt;
+ struct s5p_mfc_buf dst_bufs[MFC_MAX_BUFFERS];
+ int dst_bufs_cnt;
+
+ unsigned int sequence;
+ unsigned long dec_dst_flag;
+ size_t dec_src_buf_size;
+
+ /* Control values */
+ int codec_mode;
+ int slice_interface;
+ int loop_filter_mpeg4;
+ int display_delay;
+ int display_delay_enable;
+ int after_packed_pb;
+ int sei_fp_parse;
+
+ int pb_count;
+ int total_dpb_count;
+ int mv_count;
+ /* Buffers */
+ struct s5p_mfc_priv_buf ctx;
+ struct s5p_mfc_priv_buf dsc;
+ struct s5p_mfc_priv_buf shm;
+
+ struct s5p_mfc_enc_params enc_params;
+
+ size_t enc_dst_buf_size;
+ size_t luma_dpb_size;
+ size_t chroma_dpb_size;
+ size_t me_buffer_size;
+ size_t tmv_buffer_size;
+
+ enum v4l2_mpeg_mfc51_video_force_frame_type force_frame_type;
+
+ struct list_head ref_queue;
+ unsigned int ref_queue_cnt;
+
+ enum v4l2_mpeg_video_multi_slice_mode slice_mode;
+ union {
+ unsigned int mb;
+ unsigned int bits;
+ } slice_size;
+
+ const struct s5p_mfc_codec_ops *c_ops;
+
+ struct v4l2_ctrl *ctrls[MFC_MAX_CTRLS];
+ struct v4l2_ctrl_handler ctrl_handler;
+ size_t scratch_buf_size;
+};
+
+/*
+ * struct s5p_mfc_fmt - structure used to store information about pixelformats
+ * used by the MFC
+ */
+struct s5p_mfc_fmt {
+ u32 fourcc;
+ u32 codec_mode;
+ enum s5p_mfc_fmt_type type;
+ u32 num_planes;
+ u32 versions;
+ u32 flags;
+};
+
+/*
+ * struct mfc_control - structure used to store information about MFC controls
+ * it is used to initialize the control framework.
+ */
+struct mfc_control {
+ __u32 id;
+ enum v4l2_ctrl_type type;
+ __u8 name[32]; /* Whatever */
+ __s32 minimum; /* Note signedness */
+ __s32 maximum;
+ __s32 step;
+ __u32 menu_skip_mask;
+ __s32 default_value;
+ __u32 flags;
+ __u32 reserved[2];
+ __u8 is_volatile;
+};
+
+/* Macro for making hardware specific calls */
+#define s5p_mfc_hw_call(f, op, args...) \
+ ((f && f->op) ? f->op(args) : (typeof(f->op(args)))(-ENODEV))
+
+#define fh_to_ctx(__fh) container_of(__fh, struct s5p_mfc_ctx, fh)
+#define ctrl_to_ctx(__ctrl) \
+ container_of((__ctrl)->handler, struct s5p_mfc_ctx, ctrl_handler)
+
+void clear_work_bit(struct s5p_mfc_ctx *ctx);
+void set_work_bit(struct s5p_mfc_ctx *ctx);
+void clear_work_bit_irqsave(struct s5p_mfc_ctx *ctx);
+void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx);
+int s5p_mfc_get_new_ctx(struct s5p_mfc_dev *dev);
+void s5p_mfc_cleanup_queue(struct list_head *lh, struct vb2_queue *vq);
+
+#define HAS_PORTNUM(dev) (dev ? (dev->variant ? \
+ (dev->variant->port_num ? 1 : 0) : 0) : 0)
+#define IS_TWOPORT(dev) (dev->variant->port_num == 2 ? 1 : 0)
+#define IS_MFCV6_PLUS(dev) (dev->variant->version >= 0x60 ? 1 : 0)
+#define IS_MFCV7_PLUS(dev) (dev->variant->version >= 0x70 ? 1 : 0)
+#define IS_MFCV8_PLUS(dev) (dev->variant->version >= 0x80 ? 1 : 0)
+#define IS_MFCV10(dev) (dev->variant->version >= 0xA0 ? 1 : 0)
+#define FW_HAS_E_MIN_SCRATCH_BUF(dev) (IS_MFCV10(dev))
+
+#define MFC_V5_BIT BIT(0)
+#define MFC_V6_BIT BIT(1)
+#define MFC_V7_BIT BIT(2)
+#define MFC_V8_BIT BIT(3)
+#define MFC_V10_BIT BIT(5)
+
+#define MFC_V5PLUS_BITS (MFC_V5_BIT | MFC_V6_BIT | MFC_V7_BIT | \
+ MFC_V8_BIT | MFC_V10_BIT)
+#define MFC_V6PLUS_BITS (MFC_V6_BIT | MFC_V7_BIT | MFC_V8_BIT | \
+ MFC_V10_BIT)
+#define MFC_V7PLUS_BITS (MFC_V7_BIT | MFC_V8_BIT | MFC_V10_BIT)
+
+#endif /* S5P_MFC_COMMON_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c
new file mode 100644
index 0000000000..6d3c92045c
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_pm.h"
+#include "s5p_mfc_ctrl.h"
+
+/* Allocate memory for firmware */
+int s5p_mfc_alloc_firmware(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_priv_buf *fw_buf = &dev->fw_buf;
+ int err;
+
+ fw_buf->size = dev->variant->buf_size->fw;
+
+ if (fw_buf->virt) {
+ mfc_err("Attempting to allocate firmware when it seems that it is already loaded\n");
+ return -ENOMEM;
+ }
+
+ err = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &dev->fw_buf);
+ if (err) {
+ mfc_err("Allocating bitprocessor buffer failed\n");
+ return err;
+ }
+
+ return 0;
+}
+
+/* Load firmware */
+int s5p_mfc_load_firmware(struct s5p_mfc_dev *dev)
+{
+ struct firmware *fw_blob;
+ int i, err = -EINVAL;
+
+ /* Firmware has to be present as a separate file or compiled
+ * into kernel. */
+ mfc_debug_enter();
+
+ if (dev->fw_get_done)
+ return 0;
+
+ for (i = MFC_FW_MAX_VERSIONS - 1; i >= 0; i--) {
+ if (!dev->variant->fw_name[i])
+ continue;
+ err = request_firmware((const struct firmware **)&fw_blob,
+ dev->variant->fw_name[i], &dev->plat_dev->dev);
+ if (!err) {
+ dev->fw_ver = (enum s5p_mfc_fw_ver) i;
+ break;
+ }
+ }
+
+ if (err != 0) {
+ mfc_err("Firmware is not present in the /lib/firmware directory nor compiled in kernel\n");
+ return -EINVAL;
+ }
+ if (fw_blob->size > dev->fw_buf.size) {
+ mfc_err("MFC firmware is too big to be loaded\n");
+ release_firmware(fw_blob);
+ return -ENOMEM;
+ }
+ memcpy(dev->fw_buf.virt, fw_blob->data, fw_blob->size);
+ wmb();
+ dev->fw_get_done = true;
+ release_firmware(fw_blob);
+ mfc_debug_leave();
+ return 0;
+}
+
+/* Release firmware memory */
+int s5p_mfc_release_firmware(struct s5p_mfc_dev *dev)
+{
+ /* Before calling this function one has to make sure
+ * that MFC is no longer processing */
+ s5p_mfc_release_priv_buf(dev, &dev->fw_buf);
+ dev->fw_get_done = false;
+ return 0;
+}
+
+static int s5p_mfc_bus_reset(struct s5p_mfc_dev *dev)
+{
+ unsigned int status;
+ unsigned long timeout;
+
+ /* Reset */
+ mfc_write(dev, 0x1, S5P_FIMV_MFC_BUS_RESET_CTRL);
+ timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT);
+ /* Check bus status */
+ do {
+ if (time_after(jiffies, timeout)) {
+ mfc_err("Timeout while resetting MFC.\n");
+ return -EIO;
+ }
+ status = mfc_read(dev, S5P_FIMV_MFC_BUS_RESET_CTRL);
+ } while ((status & 0x2) == 0);
+ return 0;
+}
+
+/* Reset the device */
+int s5p_mfc_reset(struct s5p_mfc_dev *dev)
+{
+ unsigned int mc_status;
+ unsigned long timeout;
+ int i;
+
+ mfc_debug_enter();
+
+ if (IS_MFCV6_PLUS(dev)) {
+ /* Zero Initialization of MFC registers */
+ mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD_V6);
+ mfc_write(dev, 0, S5P_FIMV_HOST2RISC_CMD_V6);
+ mfc_write(dev, 0, S5P_FIMV_FW_VERSION_V6);
+
+ for (i = 0; i < S5P_FIMV_REG_CLEAR_COUNT_V6; i++)
+ mfc_write(dev, 0, S5P_FIMV_REG_CLEAR_BEGIN_V6 + (i*4));
+
+ /* check bus reset control before reset */
+ if (dev->risc_on)
+ if (s5p_mfc_bus_reset(dev))
+ return -EIO;
+ /* Reset
+ * set RISC_ON to 0 during power_on & wake_up.
+ * V6 needs RISC_ON set to 0 during reset also.
+ */
+ if ((!dev->risc_on) || (!IS_MFCV7_PLUS(dev)))
+ mfc_write(dev, 0, S5P_FIMV_RISC_ON_V6);
+
+ mfc_write(dev, 0x1FFF, S5P_FIMV_MFC_RESET_V6);
+ mfc_write(dev, 0, S5P_FIMV_MFC_RESET_V6);
+ } else {
+ /* Stop procedure */
+ /* reset RISC */
+ mfc_write(dev, 0x3f6, S5P_FIMV_SW_RESET);
+ /* All reset except for MC */
+ mfc_write(dev, 0x3e2, S5P_FIMV_SW_RESET);
+ mdelay(10);
+
+ timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT);
+ /* Check MC status */
+ do {
+ if (time_after(jiffies, timeout)) {
+ mfc_err("Timeout while resetting MFC\n");
+ return -EIO;
+ }
+
+ mc_status = mfc_read(dev, S5P_FIMV_MC_STATUS);
+
+ } while (mc_status & 0x3);
+
+ mfc_write(dev, 0x0, S5P_FIMV_SW_RESET);
+ mfc_write(dev, 0x3fe, S5P_FIMV_SW_RESET);
+ }
+
+ mfc_debug_leave();
+ return 0;
+}
+
+static inline void s5p_mfc_init_memctrl(struct s5p_mfc_dev *dev)
+{
+ if (IS_MFCV6_PLUS(dev)) {
+ mfc_write(dev, dev->dma_base[BANK_L_CTX],
+ S5P_FIMV_RISC_BASE_ADDRESS_V6);
+ mfc_debug(2, "Base Address : %pad\n",
+ &dev->dma_base[BANK_L_CTX]);
+ } else {
+ mfc_write(dev, dev->dma_base[BANK_L_CTX],
+ S5P_FIMV_MC_DRAMBASE_ADR_A);
+ mfc_write(dev, dev->dma_base[BANK_R_CTX],
+ S5P_FIMV_MC_DRAMBASE_ADR_B);
+ mfc_debug(2, "Bank1: %pad, Bank2: %pad\n",
+ &dev->dma_base[BANK_L_CTX],
+ &dev->dma_base[BANK_R_CTX]);
+ }
+}
+
+static inline void s5p_mfc_clear_cmds(struct s5p_mfc_dev *dev)
+{
+ if (IS_MFCV6_PLUS(dev)) {
+ /* Zero initialization should be done before RESET.
+ * Nothing to do here. */
+ } else {
+ mfc_write(dev, 0xffffffff, S5P_FIMV_SI_CH0_INST_ID);
+ mfc_write(dev, 0xffffffff, S5P_FIMV_SI_CH1_INST_ID);
+ mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD);
+ mfc_write(dev, 0, S5P_FIMV_HOST2RISC_CMD);
+ }
+}
+
+/* Initialize hardware */
+int s5p_mfc_init_hw(struct s5p_mfc_dev *dev)
+{
+ unsigned int ver;
+ int ret;
+
+ mfc_debug_enter();
+ if (!dev->fw_buf.virt) {
+ mfc_err("Firmware memory is not allocated.\n");
+ return -EINVAL;
+ }
+
+ /* 0. MFC reset */
+ mfc_debug(2, "MFC reset..\n");
+ s5p_mfc_clock_on();
+ dev->risc_on = 0;
+ ret = s5p_mfc_reset(dev);
+ if (ret) {
+ mfc_err("Failed to reset MFC - timeout\n");
+ return ret;
+ }
+ mfc_debug(2, "Done MFC reset..\n");
+ /* 1. Set DRAM base Addr */
+ s5p_mfc_init_memctrl(dev);
+ /* 2. Initialize registers of channel I/F */
+ s5p_mfc_clear_cmds(dev);
+ /* 3. Release reset signal to the RISC */
+ s5p_mfc_clean_dev_int_flags(dev);
+ if (IS_MFCV6_PLUS(dev)) {
+ dev->risc_on = 1;
+ mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6);
+ }
+ else
+ mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET);
+
+ if (IS_MFCV10(dev))
+ mfc_write(dev, 0x0, S5P_FIMV_MFC_CLOCK_OFF_V10);
+
+ mfc_debug(2, "Will now wait for completion of firmware transfer\n");
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_FW_STATUS_RET)) {
+ mfc_err("Failed to load firmware\n");
+ s5p_mfc_reset(dev);
+ s5p_mfc_clock_off();
+ return -EIO;
+ }
+ s5p_mfc_clean_dev_int_flags(dev);
+ /* 4. Initialize firmware */
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, sys_init_cmd, dev);
+ if (ret) {
+ mfc_err("Failed to send command to MFC - timeout\n");
+ s5p_mfc_reset(dev);
+ s5p_mfc_clock_off();
+ return ret;
+ }
+ mfc_debug(2, "Ok, now will wait for completion of hardware init\n");
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_SYS_INIT_RET)) {
+ mfc_err("Failed to init hardware\n");
+ s5p_mfc_reset(dev);
+ s5p_mfc_clock_off();
+ return -EIO;
+ }
+ dev->int_cond = 0;
+ if (dev->int_err != 0 || dev->int_type !=
+ S5P_MFC_R2H_CMD_SYS_INIT_RET) {
+ /* Failure. */
+ mfc_err("Failed to init firmware - error: %d int: %d\n",
+ dev->int_err, dev->int_type);
+ s5p_mfc_reset(dev);
+ s5p_mfc_clock_off();
+ return -EIO;
+ }
+ if (IS_MFCV6_PLUS(dev))
+ ver = mfc_read(dev, S5P_FIMV_FW_VERSION_V6);
+ else
+ ver = mfc_read(dev, S5P_FIMV_FW_VERSION);
+
+ mfc_debug(2, "MFC F/W version : %02xyy, %02xmm, %02xdd\n",
+ (ver >> 16) & 0xFF, (ver >> 8) & 0xFF, ver & 0xFF);
+ s5p_mfc_clock_off();
+ mfc_debug_leave();
+ return 0;
+}
+
+
+/* Deinitialize hardware */
+void s5p_mfc_deinit_hw(struct s5p_mfc_dev *dev)
+{
+ s5p_mfc_clock_on();
+
+ s5p_mfc_reset(dev);
+ s5p_mfc_hw_call(dev->mfc_ops, release_dev_context_buffer, dev);
+
+ s5p_mfc_clock_off();
+}
+
+int s5p_mfc_sleep(struct s5p_mfc_dev *dev)
+{
+ int ret;
+
+ mfc_debug_enter();
+ s5p_mfc_clock_on();
+ s5p_mfc_clean_dev_int_flags(dev);
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, sleep_cmd, dev);
+ if (ret) {
+ mfc_err("Failed to send command to MFC - timeout\n");
+ return ret;
+ }
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_SLEEP_RET)) {
+ mfc_err("Failed to sleep\n");
+ return -EIO;
+ }
+ s5p_mfc_clock_off();
+ dev->int_cond = 0;
+ if (dev->int_err != 0 || dev->int_type !=
+ S5P_MFC_R2H_CMD_SLEEP_RET) {
+ /* Failure. */
+ mfc_err("Failed to sleep - error: %d int: %d\n", dev->int_err,
+ dev->int_type);
+ return -EIO;
+ }
+ mfc_debug_leave();
+ return ret;
+}
+
+static int s5p_mfc_v8_wait_wakeup(struct s5p_mfc_dev *dev)
+{
+ int ret;
+
+ /* Release reset signal to the RISC */
+ dev->risc_on = 1;
+ mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6);
+
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_FW_STATUS_RET)) {
+ mfc_err("Failed to reset MFCV8\n");
+ return -EIO;
+ }
+ mfc_debug(2, "Write command to wakeup MFCV8\n");
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, wakeup_cmd, dev);
+ if (ret) {
+ mfc_err("Failed to send command to MFCV8 - timeout\n");
+ return ret;
+ }
+
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_WAKEUP_RET)) {
+ mfc_err("Failed to wakeup MFC\n");
+ return -EIO;
+ }
+ return ret;
+}
+
+static int s5p_mfc_wait_wakeup(struct s5p_mfc_dev *dev)
+{
+ int ret;
+
+ /* Send MFC wakeup command */
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, wakeup_cmd, dev);
+ if (ret) {
+ mfc_err("Failed to send command to MFC - timeout\n");
+ return ret;
+ }
+
+ /* Release reset signal to the RISC */
+ if (IS_MFCV6_PLUS(dev)) {
+ dev->risc_on = 1;
+ mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6);
+ } else {
+ mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET);
+ }
+
+ if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_WAKEUP_RET)) {
+ mfc_err("Failed to wakeup MFC\n");
+ return -EIO;
+ }
+ return ret;
+}
+
+int s5p_mfc_wakeup(struct s5p_mfc_dev *dev)
+{
+ int ret;
+
+ mfc_debug_enter();
+ /* 0. MFC reset */
+ mfc_debug(2, "MFC reset..\n");
+ s5p_mfc_clock_on();
+ dev->risc_on = 0;
+ ret = s5p_mfc_reset(dev);
+ if (ret) {
+ mfc_err("Failed to reset MFC - timeout\n");
+ s5p_mfc_clock_off();
+ return ret;
+ }
+ mfc_debug(2, "Done MFC reset..\n");
+ /* 1. Set DRAM base Addr */
+ s5p_mfc_init_memctrl(dev);
+ /* 2. Initialize registers of channel I/F */
+ s5p_mfc_clear_cmds(dev);
+ s5p_mfc_clean_dev_int_flags(dev);
+ /* 3. Send MFC wakeup command and wait for completion*/
+ if (IS_MFCV8_PLUS(dev))
+ ret = s5p_mfc_v8_wait_wakeup(dev);
+ else
+ ret = s5p_mfc_wait_wakeup(dev);
+
+ s5p_mfc_clock_off();
+ if (ret)
+ return ret;
+
+ dev->int_cond = 0;
+ if (dev->int_err != 0 || dev->int_type !=
+ S5P_MFC_R2H_CMD_WAKEUP_RET) {
+ /* Failure. */
+ mfc_err("Failed to wakeup - error: %d int: %d\n", dev->int_err,
+ dev->int_type);
+ return -EIO;
+ }
+ mfc_debug_leave();
+ return 0;
+}
+
+int s5p_mfc_open_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx)
+{
+ int ret = 0;
+
+ ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_instance_buffer, ctx);
+ if (ret) {
+ mfc_err("Failed allocating instance buffer\n");
+ goto err;
+ }
+
+ if (ctx->type == MFCINST_DECODER) {
+ ret = s5p_mfc_hw_call(dev->mfc_ops,
+ alloc_dec_temp_buffers, ctx);
+ if (ret) {
+ mfc_err("Failed allocating temporary buffers\n");
+ goto err_free_inst_buf;
+ }
+ }
+
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ if (s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET, 0)) {
+ /* Error or timeout */
+ mfc_err("Error getting instance from hardware\n");
+ ret = -EIO;
+ goto err_free_desc_buf;
+ }
+
+ mfc_debug(2, "Got instance number: %d\n", ctx->inst_no);
+ return ret;
+
+err_free_desc_buf:
+ if (ctx->type == MFCINST_DECODER)
+ s5p_mfc_hw_call(dev->mfc_ops, release_dec_desc_buffer, ctx);
+err_free_inst_buf:
+ s5p_mfc_hw_call(dev->mfc_ops, release_instance_buffer, ctx);
+err:
+ return ret;
+}
+
+void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx)
+{
+ ctx->state = MFCINST_RETURN_INST;
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ /* Wait until instance is returned or timeout occurred */
+ if (s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)){
+ clear_work_bit_irqsave(ctx);
+ mfc_err("Err returning instance\n");
+ }
+
+ /* Free resources */
+ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, release_instance_buffer, ctx);
+ if (ctx->type == MFCINST_DECODER)
+ s5p_mfc_hw_call(dev->mfc_ops, release_dec_desc_buffer, ctx);
+
+ ctx->inst_no = MFC_NO_INSTANCE_SET;
+ ctx->state = MFCINST_FREE;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h
new file mode 100644
index 0000000000..653ba5f3d0
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_CTRL_H
+#define S5P_MFC_CTRL_H
+
+#include "s5p_mfc_common.h"
+
+int s5p_mfc_release_firmware(struct s5p_mfc_dev *dev);
+int s5p_mfc_alloc_firmware(struct s5p_mfc_dev *dev);
+int s5p_mfc_load_firmware(struct s5p_mfc_dev *dev);
+
+int s5p_mfc_init_hw(struct s5p_mfc_dev *dev);
+void s5p_mfc_deinit_hw(struct s5p_mfc_dev *dev);
+
+int s5p_mfc_sleep(struct s5p_mfc_dev *dev);
+int s5p_mfc_wakeup(struct s5p_mfc_dev *dev);
+
+int s5p_mfc_reset(struct s5p_mfc_dev *dev);
+
+int s5p_mfc_open_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx);
+void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx);
+
+#endif /* S5P_MFC_CTRL_H */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h
new file mode 100644
index 0000000000..bba5dad6db
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h
+ *
+ * Header file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * This file contains debug macros
+ *
+ * Kamil Debski, Copyright (c) 2011 Samsung Electronics
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_DEBUG_H_
+#define S5P_MFC_DEBUG_H_
+
+#define DEBUG
+
+#ifdef DEBUG
+extern int mfc_debug_level;
+
+#define mfc_debug(level, fmt, args...) \
+ do { \
+ if (mfc_debug_level >= level) \
+ printk(KERN_DEBUG "%s:%d: " fmt, \
+ __func__, __LINE__, ##args); \
+ } while (0)
+#else
+#define mfc_debug(level, fmt, args...)
+#endif
+
+#define mfc_debug_enter() mfc_debug(5, "enter\n")
+#define mfc_debug_leave() mfc_debug(5, "leave\n")
+
+#define mfc_err(fmt, args...) \
+ do { \
+ printk(KERN_ERR "%s:%d: " fmt, \
+ __func__, __LINE__, ##args); \
+ } while (0)
+
+#define mfc_err_limited(fmt, args...) \
+ do { \
+ printk_ratelimited(KERN_ERR "%s:%d: " fmt, \
+ __func__, __LINE__, ##args); \
+ } while (0)
+
+#define mfc_info(fmt, args...) \
+ do { \
+ printk(KERN_INFO "%s:%d: " fmt, \
+ __func__, __LINE__, ##args); \
+ } while (0)
+
+#endif /* S5P_MFC_DEBUG_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c
new file mode 100644
index 0000000000..268ffe4da5
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c
@@ -0,0 +1,1216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ * Kamil Debski, <k.debski@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_ctrl.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_dec.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_pm.h"
+
+static struct s5p_mfc_fmt formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_NV12MT_16X16,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V6_BIT | MFC_V7_BIT,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12MT,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V5_BIT,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V6PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V6PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_H264,
+ .codec_mode = S5P_MFC_CODEC_H264_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_H264_MVC,
+ .codec_mode = S5P_MFC_CODEC_H264_MVC_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V6PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_H263,
+ .codec_mode = S5P_MFC_CODEC_H263_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_MPEG1,
+ .codec_mode = S5P_MFC_CODEC_MPEG2_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_MPEG2,
+ .codec_mode = S5P_MFC_CODEC_MPEG2_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_MPEG4,
+ .codec_mode = S5P_MFC_CODEC_MPEG4_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_XVID,
+ .codec_mode = S5P_MFC_CODEC_MPEG4_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VC1_ANNEX_G,
+ .codec_mode = S5P_MFC_CODEC_VC1_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VC1_ANNEX_L,
+ .codec_mode = S5P_MFC_CODEC_VC1RCV_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VP8,
+ .codec_mode = S5P_MFC_CODEC_VP8_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V6PLUS_BITS,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_HEVC,
+ .codec_mode = S5P_FIMV_CODEC_HEVC_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V10_BIT,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION |
+ V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VP9,
+ .codec_mode = S5P_FIMV_CODEC_VP9_DEC,
+ .type = MFC_FMT_DEC,
+ .num_planes = 1,
+ .versions = MFC_V10_BIT,
+ .flags = V4L2_FMT_FLAG_DYN_RESOLUTION,
+ },
+};
+
+#define NUM_FORMATS ARRAY_SIZE(formats)
+
+/* Find selected format description */
+static struct s5p_mfc_fmt *find_format(struct v4l2_format *f, unsigned int t)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].fourcc == f->fmt.pix_mp.pixelformat &&
+ formats[i].type == t)
+ return &formats[i];
+ }
+ return NULL;
+}
+
+static struct mfc_control controls[] = {
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H264 Display Delay",
+ .minimum = 0,
+ .maximum = 16383,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 16383,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H264 Display Delay Enable",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mpeg4 Loop Filter Enable",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Slice Interface Enable",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Minimum number of cap bufs",
+ .minimum = 1,
+ .maximum = 32,
+ .step = 1,
+ .default_value = 1,
+ .is_volatile = 1,
+ },
+};
+
+#define NUM_CTRLS ARRAY_SIZE(controls)
+
+/* Check whether a context should be run on hardware */
+static int s5p_mfc_ctx_ready(struct s5p_mfc_ctx *ctx)
+{
+ /* Context is to parse header */
+ if (ctx->src_queue_cnt >= 1 && ctx->state == MFCINST_GOT_INST)
+ return 1;
+ /* Context is to decode a frame */
+ if (ctx->src_queue_cnt >= 1 &&
+ ctx->state == MFCINST_RUNNING &&
+ ctx->dst_queue_cnt >= ctx->pb_count)
+ return 1;
+ /* Context is to return last frame */
+ if (ctx->state == MFCINST_FINISHING &&
+ ctx->dst_queue_cnt >= ctx->pb_count)
+ return 1;
+ /* Context is to set buffers */
+ if (ctx->src_queue_cnt >= 1 &&
+ ctx->state == MFCINST_HEAD_PARSED &&
+ ctx->capture_state == QUEUE_BUFS_MMAPED)
+ return 1;
+ /* Resolution change */
+ if ((ctx->state == MFCINST_RES_CHANGE_INIT ||
+ ctx->state == MFCINST_RES_CHANGE_FLUSH) &&
+ ctx->dst_queue_cnt >= ctx->pb_count)
+ return 1;
+ if (ctx->state == MFCINST_RES_CHANGE_END &&
+ ctx->src_queue_cnt >= 1)
+ return 1;
+ mfc_debug(2, "ctx is not ready\n");
+ return 0;
+}
+
+static const struct s5p_mfc_codec_ops decoder_codec_ops = {
+ .pre_seq_start = NULL,
+ .post_seq_start = NULL,
+ .pre_frame_start = NULL,
+ .post_frame_start = NULL,
+};
+
+/* Query capabilities of the device */
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+
+ strscpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver));
+ strscpy(cap->card, dev->vfd_dec->name, sizeof(cap->card));
+ return 0;
+}
+
+/* Enumerate format */
+static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *f,
+ bool out)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ int i, j = 0;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (out && formats[i].type != MFC_FMT_DEC)
+ continue;
+ else if (!out && formats[i].type != MFC_FMT_RAW)
+ continue;
+ else if ((dev->variant->version_bit & formats[i].versions) == 0)
+ continue;
+
+ if (j == f->index)
+ break;
+ ++j;
+ }
+ if (i == ARRAY_SIZE(formats))
+ return -EINVAL;
+ f->pixelformat = formats[i].fourcc;
+ return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *pirv,
+ struct v4l2_fmtdesc *f)
+{
+ return vidioc_enum_fmt(file, f, false);
+}
+
+static int vidioc_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return vidioc_enum_fmt(file, f, true);
+}
+
+/* Get format */
+static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct v4l2_pix_format_mplane *pix_mp;
+
+ mfc_debug_enter();
+ pix_mp = &f->fmt.pix_mp;
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ (ctx->state == MFCINST_GOT_INST || ctx->state ==
+ MFCINST_RES_CHANGE_END)) {
+ /* If the MFC is parsing the header,
+ * so wait until it is finished */
+ s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_SEQ_DONE_RET,
+ 0);
+ }
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+ ctx->state >= MFCINST_HEAD_PARSED &&
+ ctx->state < MFCINST_ABORT) {
+ /* This is run on CAPTURE (decode output) */
+ /* Width and height are set to the dimensions
+ of the movie, the buffer is bigger and
+ further processing stages should crop to this
+ rectangle. */
+ pix_mp->width = ctx->buf_width;
+ pix_mp->height = ctx->buf_height;
+ pix_mp->field = V4L2_FIELD_NONE;
+ pix_mp->num_planes = 2;
+ /* Set pixelformat to the format in which MFC
+ outputs the decoded frame */
+ pix_mp->pixelformat = ctx->dst_fmt->fourcc;
+ pix_mp->plane_fmt[0].bytesperline = ctx->buf_width;
+ pix_mp->plane_fmt[0].sizeimage = ctx->luma_size;
+ pix_mp->plane_fmt[1].bytesperline = ctx->buf_width;
+ pix_mp->plane_fmt[1].sizeimage = ctx->chroma_size;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ /* This is run on OUTPUT
+ The buffer contains compressed image
+ so width and height have no meaning */
+ pix_mp->width = 0;
+ pix_mp->height = 0;
+ pix_mp->field = V4L2_FIELD_NONE;
+ pix_mp->plane_fmt[0].bytesperline = ctx->dec_src_buf_size;
+ pix_mp->plane_fmt[0].sizeimage = ctx->dec_src_buf_size;
+ pix_mp->pixelformat = ctx->src_fmt->fourcc;
+ pix_mp->num_planes = ctx->src_fmt->num_planes;
+ } else {
+ mfc_err("Format could not be read\n");
+ mfc_debug(2, "%s-- with error\n", __func__);
+ return -EINVAL;
+ }
+ mfc_debug_leave();
+ return 0;
+}
+
+/* Try format */
+static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_fmt *fmt;
+
+ mfc_debug(2, "Type is %d\n", f->type);
+ if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ fmt = find_format(f, MFC_FMT_DEC);
+ if (!fmt) {
+ mfc_err("Unsupported format for source.\n");
+ return -EINVAL;
+ }
+ if (fmt->codec_mode == S5P_FIMV_CODEC_NONE) {
+ mfc_err("Unknown codec\n");
+ return -EINVAL;
+ }
+ if ((dev->variant->version_bit & fmt->versions) == 0) {
+ mfc_err("Unsupported format by this MFC version.\n");
+ return -EINVAL;
+ }
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fmt = find_format(f, MFC_FMT_RAW);
+ if (!fmt) {
+ mfc_err("Unsupported format for destination.\n");
+ return -EINVAL;
+ }
+ if ((dev->variant->version_bit & fmt->versions) == 0) {
+ mfc_err("Unsupported format by this MFC version.\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* Set format */
+static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret = 0;
+ struct v4l2_pix_format_mplane *pix_mp;
+ struct s5p_mfc_buf_size *buf_size = dev->variant->buf_size;
+
+ mfc_debug_enter();
+ ret = vidioc_try_fmt(file, priv, f);
+ pix_mp = &f->fmt.pix_mp;
+ if (ret)
+ return ret;
+ if (vb2_is_streaming(&ctx->vq_src) || vb2_is_streaming(&ctx->vq_dst)) {
+ v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__);
+ ret = -EBUSY;
+ goto out;
+ }
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ /* dst_fmt is validated by call to vidioc_try_fmt */
+ ctx->dst_fmt = find_format(f, MFC_FMT_RAW);
+ ret = 0;
+ goto out;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ /* src_fmt is validated by call to vidioc_try_fmt */
+ ctx->src_fmt = find_format(f, MFC_FMT_DEC);
+ ctx->codec_mode = ctx->src_fmt->codec_mode;
+ mfc_debug(2, "The codec number is: %d\n", ctx->codec_mode);
+ pix_mp->height = 0;
+ pix_mp->width = 0;
+ if (pix_mp->plane_fmt[0].sizeimage == 0)
+ pix_mp->plane_fmt[0].sizeimage = ctx->dec_src_buf_size =
+ DEF_CPB_SIZE;
+ else if (pix_mp->plane_fmt[0].sizeimage > buf_size->cpb)
+ ctx->dec_src_buf_size = buf_size->cpb;
+ else
+ ctx->dec_src_buf_size = pix_mp->plane_fmt[0].sizeimage;
+ pix_mp->plane_fmt[0].bytesperline = 0;
+ ctx->state = MFCINST_INIT;
+ ret = 0;
+ goto out;
+ } else {
+ mfc_err("Wrong type error for S_FMT : %d", f->type);
+ ret = -EINVAL;
+ goto out;
+ }
+
+out:
+ mfc_debug_leave();
+ return ret;
+}
+
+static int reqbufs_output(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ int ret = 0;
+
+ s5p_mfc_clock_on();
+
+ if (reqbufs->count == 0) {
+ mfc_debug(2, "Freeing buffers\n");
+ ret = vb2_reqbufs(&ctx->vq_src, reqbufs);
+ if (ret)
+ goto out;
+ ctx->src_bufs_cnt = 0;
+ ctx->output_state = QUEUE_FREE;
+ } else if (ctx->output_state == QUEUE_FREE) {
+ /* Can only request buffers when we have a valid format set. */
+ WARN_ON(ctx->src_bufs_cnt != 0);
+ if (ctx->state != MFCINST_INIT) {
+ mfc_err("Reqbufs called in an invalid state\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mfc_debug(2, "Allocating %d buffers for OUTPUT queue\n",
+ reqbufs->count);
+ ret = vb2_reqbufs(&ctx->vq_src, reqbufs);
+ if (ret)
+ goto out;
+
+ ret = s5p_mfc_open_mfc_inst(dev, ctx);
+ if (ret) {
+ reqbufs->count = 0;
+ vb2_reqbufs(&ctx->vq_src, reqbufs);
+ goto out;
+ }
+
+ ctx->output_state = QUEUE_BUFS_REQUESTED;
+ } else {
+ mfc_err("Buffers have already been requested\n");
+ ret = -EINVAL;
+ }
+out:
+ s5p_mfc_clock_off();
+ if (ret)
+ mfc_err("Failed allocating buffers for OUTPUT queue\n");
+ return ret;
+}
+
+static int reqbufs_capture(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ int ret = 0;
+
+ s5p_mfc_clock_on();
+
+ if (reqbufs->count == 0) {
+ mfc_debug(2, "Freeing buffers\n");
+ ret = vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ if (ret)
+ goto out;
+ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx);
+ ctx->dst_bufs_cnt = 0;
+ } else if (ctx->capture_state == QUEUE_FREE) {
+ WARN_ON(ctx->dst_bufs_cnt != 0);
+ mfc_debug(2, "Allocating %d buffers for CAPTURE queue\n",
+ reqbufs->count);
+ ret = vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ if (ret)
+ goto out;
+
+ ctx->capture_state = QUEUE_BUFS_REQUESTED;
+ ctx->total_dpb_count = reqbufs->count;
+
+ ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_codec_buffers, ctx);
+ if (ret) {
+ mfc_err("Failed to allocate decoding buffers\n");
+ reqbufs->count = 0;
+ vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ ret = -ENOMEM;
+ ctx->capture_state = QUEUE_FREE;
+ goto out;
+ }
+
+ WARN_ON(ctx->dst_bufs_cnt != ctx->total_dpb_count);
+ ctx->capture_state = QUEUE_BUFS_MMAPED;
+
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_INIT_BUFFERS_RET,
+ 0);
+ } else {
+ mfc_err("Buffers have already been requested\n");
+ ret = -EINVAL;
+ }
+out:
+ s5p_mfc_clock_off();
+ if (ret)
+ mfc_err("Failed allocating buffers for CAPTURE queue\n");
+ return ret;
+}
+
+/* Request buffers */
+static int vidioc_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (reqbufs->memory != V4L2_MEMORY_MMAP) {
+ mfc_debug(2, "Only V4L2_MEMORY_MMAP is supported\n");
+ return -EINVAL;
+ }
+
+ if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ return reqbufs_output(dev, ctx, reqbufs);
+ } else if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ return reqbufs_capture(dev, ctx, reqbufs);
+ } else {
+ mfc_err("Invalid type requested\n");
+ return -EINVAL;
+ }
+}
+
+/* Query buffer */
+static int vidioc_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret;
+ int i;
+
+ if (buf->memory != V4L2_MEMORY_MMAP) {
+ mfc_err("Only mmapped buffers can be used\n");
+ return -EINVAL;
+ }
+ mfc_debug(2, "State: %d, buf->type: %d\n", ctx->state, buf->type);
+ if (ctx->state == MFCINST_GOT_INST &&
+ buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = vb2_querybuf(&ctx->vq_src, buf);
+ } else if (ctx->state == MFCINST_RUNNING &&
+ buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ ret = vb2_querybuf(&ctx->vq_dst, buf);
+ for (i = 0; i < buf->length; i++)
+ buf->m.planes[i].m.mem_offset += DST_QUEUE_OFF_BASE;
+ } else {
+ mfc_err("vidioc_querybuf called in an inappropriate state\n");
+ ret = -EINVAL;
+ }
+ mfc_debug_leave();
+ return ret;
+}
+
+/* Queue a buffer */
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (ctx->state == MFCINST_ERROR) {
+ mfc_err("Call on QBUF after unrecoverable error\n");
+ return -EIO;
+ }
+ if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_qbuf(&ctx->vq_src, NULL, buf);
+ else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_qbuf(&ctx->vq_dst, NULL, buf);
+ return -EINVAL;
+}
+
+/* Dequeue a buffer */
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ const struct v4l2_event ev = {
+ .type = V4L2_EVENT_EOS
+ };
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret;
+
+ if (ctx->state == MFCINST_ERROR) {
+ mfc_err_limited("Call on DQBUF after unrecoverable error\n");
+ return -EIO;
+ }
+
+ switch (buf->type) {
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ return vb2_dqbuf(&ctx->vq_src, buf, file->f_flags & O_NONBLOCK);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ ret = vb2_dqbuf(&ctx->vq_dst, buf, file->f_flags & O_NONBLOCK);
+ if (ret)
+ return ret;
+
+ if (ctx->state == MFCINST_FINISHED &&
+ (ctx->dst_bufs[buf->index].flags & MFC_BUF_FLAG_EOS))
+ v4l2_event_queue_fh(&ctx->fh, &ev);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Export DMA buffer */
+static int vidioc_expbuf(struct file *file, void *priv,
+ struct v4l2_exportbuffer *eb)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (eb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_expbuf(&ctx->vq_src, eb);
+ if (eb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_expbuf(&ctx->vq_dst, eb);
+ return -EINVAL;
+}
+
+/* Stream on */
+static int vidioc_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret = -EINVAL;
+
+ mfc_debug_enter();
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ ret = vb2_streamon(&ctx->vq_src, type);
+ else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ ret = vb2_streamon(&ctx->vq_dst, type);
+ mfc_debug_leave();
+ return ret;
+}
+
+/* Stream off, which equals to a pause */
+static int vidioc_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_streamoff(&ctx->vq_src, type);
+ else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_streamoff(&ctx->vq_dst, type);
+ return -EINVAL;
+}
+
+/* Set controls - v4l2 control framework */
+static int s5p_mfc_dec_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY:
+ case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY:
+ ctx->display_delay = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY_ENABLE:
+ case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE:
+ ctx->display_delay_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER:
+ ctx->loop_filter_mpeg4 = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE:
+ ctx->slice_interface = ctrl->val;
+ break;
+ default:
+ mfc_err("Invalid control 0x%08x\n", ctrl->id);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_dec_g_v_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+ if (ctx->state >= MFCINST_HEAD_PARSED &&
+ ctx->state < MFCINST_ABORT) {
+ ctrl->val = ctx->pb_count;
+ break;
+ } else if (ctx->state != MFCINST_INIT &&
+ ctx->state != MFCINST_RES_CHANGE_END) {
+ v4l2_err(&dev->v4l2_dev, "Decoding not initialised\n");
+ return -EINVAL;
+ }
+ /* Should wait for the header to be parsed */
+ s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_SEQ_DONE_RET, 0);
+ if (ctx->state >= MFCINST_HEAD_PARSED &&
+ ctx->state < MFCINST_ABORT) {
+ ctrl->val = ctx->pb_count;
+ } else {
+ v4l2_err(&dev->v4l2_dev, "Decoding not initialised\n");
+ return -EINVAL;
+ }
+ break;
+ }
+ return 0;
+}
+
+
+static const struct v4l2_ctrl_ops s5p_mfc_dec_ctrl_ops = {
+ .s_ctrl = s5p_mfc_dec_s_ctrl,
+ .g_volatile_ctrl = s5p_mfc_dec_g_v_ctrl,
+};
+
+/* Get compose information */
+static int vidioc_g_selection(struct file *file, void *priv,
+ struct v4l2_selection *s)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ u32 left, right, top, bottom;
+ u32 width, height;
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ if (ctx->state != MFCINST_HEAD_PARSED &&
+ ctx->state != MFCINST_RUNNING &&
+ ctx->state != MFCINST_FINISHING &&
+ ctx->state != MFCINST_FINISHED) {
+ mfc_err("Can not get compose information\n");
+ return -EINVAL;
+ }
+ if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_H264) {
+ left = s5p_mfc_hw_call(dev->mfc_ops, get_crop_info_h, ctx);
+ right = left >> S5P_FIMV_SHARED_CROP_RIGHT_SHIFT;
+ left = left & S5P_FIMV_SHARED_CROP_LEFT_MASK;
+ top = s5p_mfc_hw_call(dev->mfc_ops, get_crop_info_v, ctx);
+ bottom = top >> S5P_FIMV_SHARED_CROP_BOTTOM_SHIFT;
+ top = top & S5P_FIMV_SHARED_CROP_TOP_MASK;
+ width = ctx->img_width - left - right;
+ height = ctx->img_height - top - bottom;
+ mfc_debug(2, "Composing info [h264]: l=%d t=%d w=%d h=%d (r=%d b=%d fw=%d fh=%d\n",
+ left, top, s->r.width, s->r.height, right, bottom,
+ ctx->buf_width, ctx->buf_height);
+ } else {
+ left = 0;
+ top = 0;
+ width = ctx->img_width;
+ height = ctx->img_height;
+ mfc_debug(2, "Composing info: w=%d h=%d fw=%d fh=%d\n",
+ s->r.width, s->r.height, ctx->buf_width,
+ ctx->buf_height);
+ }
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE:
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ s->r.left = left;
+ s->r.top = top;
+ s->r.width = width;
+ s->r.height = height;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_decoder_cmd(struct file *file, void *priv,
+ struct v4l2_decoder_cmd *cmd)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *buf;
+ unsigned long flags;
+
+ switch (cmd->cmd) {
+ case V4L2_DEC_CMD_STOP:
+ if (cmd->flags != 0)
+ return -EINVAL;
+
+ if (!vb2_is_streaming(&ctx->vq_src))
+ return -EINVAL;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ if (list_empty(&ctx->src_queue)) {
+ mfc_err("EOS: empty src queue, entering finishing state");
+ ctx->state = MFCINST_FINISHING;
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ } else {
+ mfc_err("EOS: marking last buffer of stream");
+ buf = list_entry(ctx->src_queue.prev,
+ struct s5p_mfc_buf, list);
+ if (buf->flags & MFC_BUF_FLAG_USED)
+ ctx->state = MFCINST_FINISHING;
+ else
+ buf->flags |= MFC_BUF_FLAG_EOS;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_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);
+ case V4L2_EVENT_SOURCE_CHANGE:
+ return v4l2_src_change_event_subscribe(fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+
+/* v4l2_ioctl_ops */
+static const struct v4l2_ioctl_ops s5p_mfc_dec_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out,
+ .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt,
+ .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt,
+ .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt,
+ .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt,
+ .vidioc_reqbufs = vidioc_reqbufs,
+ .vidioc_querybuf = vidioc_querybuf,
+ .vidioc_qbuf = vidioc_qbuf,
+ .vidioc_dqbuf = vidioc_dqbuf,
+ .vidioc_expbuf = vidioc_expbuf,
+ .vidioc_streamon = vidioc_streamon,
+ .vidioc_streamoff = vidioc_streamoff,
+ .vidioc_g_selection = vidioc_g_selection,
+ .vidioc_decoder_cmd = vidioc_decoder_cmd,
+ .vidioc_subscribe_event = vidioc_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int s5p_mfc_queue_setup(struct vb2_queue *vq,
+ unsigned int *buf_count,
+ unsigned int *plane_count, unsigned int psize[],
+ struct device *alloc_devs[])
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ /* Video output for decoding (source)
+ * this can be set after getting an instance */
+ if (ctx->state == MFCINST_INIT &&
+ vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ /* A single plane is required for input */
+ *plane_count = 1;
+ if (*buf_count < 1)
+ *buf_count = 1;
+ if (*buf_count > MFC_MAX_BUFFERS)
+ *buf_count = MFC_MAX_BUFFERS;
+ /* Video capture for decoding (destination)
+ * this can be set after the header was parsed */
+ } else if (ctx->state == MFCINST_HEAD_PARSED &&
+ vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ /* Output plane count is 2 - one for Y and one for CbCr */
+ *plane_count = 2;
+ /* Setup buffer count */
+ if (*buf_count < ctx->pb_count)
+ *buf_count = ctx->pb_count;
+ if (*buf_count > ctx->pb_count + MFC_MAX_EXTRA_DPB)
+ *buf_count = ctx->pb_count + MFC_MAX_EXTRA_DPB;
+ if (*buf_count > MFC_MAX_BUFFERS)
+ *buf_count = MFC_MAX_BUFFERS;
+ } else {
+ mfc_err("State seems invalid. State = %d, vq->type = %d\n",
+ ctx->state, vq->type);
+ return -EINVAL;
+ }
+ mfc_debug(2, "Buffer count=%d, plane count=%d\n",
+ *buf_count, *plane_count);
+ if (ctx->state == MFCINST_HEAD_PARSED &&
+ vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ psize[0] = ctx->luma_size;
+ psize[1] = ctx->chroma_size;
+
+ if (IS_MFCV6_PLUS(dev))
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX];
+ else
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_R_CTX];
+ alloc_devs[1] = ctx->dev->mem_dev[BANK_L_CTX];
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE &&
+ ctx->state == MFCINST_INIT) {
+ psize[0] = ctx->dec_src_buf_size;
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX];
+ } else {
+ mfc_err("This video node is dedicated to decoding. Decoding not initialized\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ unsigned int i;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (ctx->capture_state == QUEUE_BUFS_MMAPED)
+ return 0;
+ for (i = 0; i < ctx->dst_fmt->num_planes; i++) {
+ if (IS_ERR_OR_NULL(ERR_PTR(
+ vb2_dma_contig_plane_dma_addr(vb, i)))) {
+ mfc_err("Plane mem not allocated\n");
+ return -EINVAL;
+ }
+ }
+ if (vb2_plane_size(vb, 0) < ctx->luma_size ||
+ vb2_plane_size(vb, 1) < ctx->chroma_size) {
+ mfc_err("Plane buffer (CAPTURE) is too small\n");
+ return -EINVAL;
+ }
+ i = vb->index;
+ ctx->dst_bufs[i].b = vbuf;
+ ctx->dst_bufs[i].cookie.raw.luma =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ ctx->dst_bufs[i].cookie.raw.chroma =
+ vb2_dma_contig_plane_dma_addr(vb, 1);
+ ctx->dst_bufs_cnt++;
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ if (IS_ERR_OR_NULL(ERR_PTR(
+ vb2_dma_contig_plane_dma_addr(vb, 0)))) {
+ mfc_err("Plane memory not allocated\n");
+ return -EINVAL;
+ }
+ if (vb2_plane_size(vb, 0) < ctx->dec_src_buf_size) {
+ mfc_err("Plane buffer (OUTPUT) is too small\n");
+ return -EINVAL;
+ }
+
+ i = vb->index;
+ ctx->src_bufs[i].b = vbuf;
+ ctx->src_bufs[i].cookie.stream =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ ctx->src_bufs_cnt++;
+ } else {
+ mfc_err("s5p_mfc_buf_init: unknown queue type\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+ if (ctx->state == MFCINST_FINISHING ||
+ ctx->state == MFCINST_FINISHED)
+ ctx->state = MFCINST_RUNNING;
+ /* If context is ready then dev = work->data;schedule it to run */
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ return 0;
+}
+
+static void s5p_mfc_stop_streaming(struct vb2_queue *q)
+{
+ unsigned long flags;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ int aborted = 0;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ if ((ctx->state == MFCINST_FINISHING ||
+ ctx->state == MFCINST_RUNNING) &&
+ dev->curr_ctx == ctx->num && dev->hw_lock) {
+ ctx->state = MFCINST_ABORT;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_FRAME_DONE_RET, 0);
+ aborted = 1;
+ spin_lock_irqsave(&dev->irqlock, flags);
+ }
+ if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
+ INIT_LIST_HEAD(&ctx->dst_queue);
+ ctx->dst_queue_cnt = 0;
+ ctx->dpb_flush_flag = 1;
+ ctx->dec_dst_flag = 0;
+ if (IS_MFCV6_PLUS(dev) && (ctx->state == MFCINST_RUNNING)) {
+ ctx->state = MFCINST_FLUSH;
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ if (s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_DPB_FLUSH_RET, 0))
+ mfc_err("Err flushing buffers\n");
+ spin_lock_irqsave(&dev->irqlock, flags);
+ }
+ } else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
+ INIT_LIST_HEAD(&ctx->src_queue);
+ ctx->src_queue_cnt = 0;
+ }
+ if (aborted)
+ ctx->state = MFCINST_RUNNING;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+
+static void s5p_mfc_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned long flags;
+ struct s5p_mfc_buf *mfc_buf;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ mfc_buf = &ctx->src_bufs[vb->index];
+ mfc_buf->flags &= ~MFC_BUF_FLAG_USED;
+ spin_lock_irqsave(&dev->irqlock, flags);
+ list_add_tail(&mfc_buf->list, &ctx->src_queue);
+ ctx->src_queue_cnt++;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ mfc_buf = &ctx->dst_bufs[vb->index];
+ mfc_buf->flags &= ~MFC_BUF_FLAG_USED;
+ /* Mark destination as available for use by MFC */
+ spin_lock_irqsave(&dev->irqlock, flags);
+ set_bit(vb->index, &ctx->dec_dst_flag);
+ list_add_tail(&mfc_buf->list, &ctx->dst_queue);
+ ctx->dst_queue_cnt++;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ } else {
+ mfc_err("Unsupported buffer type (%d)\n", vq->type);
+ }
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+}
+
+static struct vb2_ops s5p_mfc_dec_qops = {
+ .queue_setup = s5p_mfc_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_init = s5p_mfc_buf_init,
+ .start_streaming = s5p_mfc_start_streaming,
+ .stop_streaming = s5p_mfc_stop_streaming,
+ .buf_queue = s5p_mfc_buf_queue,
+};
+
+const struct s5p_mfc_codec_ops *get_dec_codec_ops(void)
+{
+ return &decoder_codec_ops;
+}
+
+struct vb2_ops *get_dec_queue_ops(void)
+{
+ return &s5p_mfc_dec_qops;
+}
+
+const struct v4l2_ioctl_ops *get_dec_v4l2_ioctl_ops(void)
+{
+ return &s5p_mfc_dec_ioctl_ops;
+}
+
+#define IS_MFC51_PRIV(x) ((V4L2_CTRL_ID2WHICH(x) == V4L2_CTRL_CLASS_CODEC) \
+ && V4L2_CTRL_DRIVER_PRIV(x))
+
+int s5p_mfc_dec_ctrls_setup(struct s5p_mfc_ctx *ctx)
+{
+ struct v4l2_ctrl_config cfg;
+ int i;
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, NUM_CTRLS);
+ if (ctx->ctrl_handler.error) {
+ mfc_err("v4l2_ctrl_handler_init failed\n");
+ return ctx->ctrl_handler.error;
+ }
+
+ for (i = 0; i < NUM_CTRLS; i++) {
+ if (IS_MFC51_PRIV(controls[i].id)) {
+ memset(&cfg, 0, sizeof(struct v4l2_ctrl_config));
+ cfg.ops = &s5p_mfc_dec_ctrl_ops;
+ cfg.id = controls[i].id;
+ cfg.min = controls[i].minimum;
+ cfg.max = controls[i].maximum;
+ cfg.def = controls[i].default_value;
+ cfg.name = controls[i].name;
+ cfg.type = controls[i].type;
+
+ cfg.step = controls[i].step;
+ cfg.menu_skip_mask = 0;
+
+ ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler,
+ &cfg, NULL);
+ } else {
+ ctx->ctrls[i] = v4l2_ctrl_new_std(&ctx->ctrl_handler,
+ &s5p_mfc_dec_ctrl_ops,
+ controls[i].id, controls[i].minimum,
+ controls[i].maximum, controls[i].step,
+ controls[i].default_value);
+ }
+ if (ctx->ctrl_handler.error) {
+ mfc_err("Adding control (%d) failed\n", i);
+ return ctx->ctrl_handler.error;
+ }
+ if (controls[i].is_volatile && ctx->ctrls[i])
+ ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ }
+ return 0;
+}
+
+void s5p_mfc_dec_ctrls_delete(struct s5p_mfc_ctx *ctx)
+{
+ int i;
+
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ for (i = 0; i < NUM_CTRLS; i++)
+ ctx->ctrls[i] = NULL;
+}
+
+void s5p_mfc_dec_init(struct s5p_mfc_ctx *ctx)
+{
+ struct v4l2_format f;
+ f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264;
+ ctx->src_fmt = find_format(&f, MFC_FMT_DEC);
+ if (IS_MFCV8_PLUS(ctx->dev))
+ f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M;
+ else if (IS_MFCV6_PLUS(ctx->dev))
+ f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12MT_16X16;
+ else
+ f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12MT;
+ ctx->dst_fmt = find_format(&f, MFC_FMT_RAW);
+ mfc_debug(2, "Default src_fmt is %p, dest_fmt is %p\n",
+ ctx->src_fmt, ctx->dst_fmt);
+}
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h
new file mode 100644
index 0000000000..0c52ab46cf
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_DEC_H_
+#define S5P_MFC_DEC_H_
+
+const struct s5p_mfc_codec_ops *get_dec_codec_ops(void);
+struct vb2_ops *get_dec_queue_ops(void);
+const struct v4l2_ioctl_ops *get_dec_v4l2_ioctl_ops(void);
+struct s5p_mfc_fmt *get_dec_def_fmt(bool src);
+int s5p_mfc_dec_ctrls_setup(struct s5p_mfc_ctx *ctx);
+void s5p_mfc_dec_ctrls_delete(struct s5p_mfc_ctx *ctx);
+void s5p_mfc_dec_init(struct s5p_mfc_ctx *ctx);
+
+#endif /* S5P_MFC_DEC_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c
new file mode 100644
index 0000000000..f62703cebb
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c
@@ -0,0 +1,2703 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Jeongtae Park <jtp.park@samsung.com>
+ * Kamil Debski <k.debski@samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-event.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-v4l2.h>
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_ctrl.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_enc.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_opr.h"
+
+#define DEF_SRC_FMT_ENC V4L2_PIX_FMT_NV12M
+#define DEF_DST_FMT_ENC V4L2_PIX_FMT_H264
+
+static struct s5p_mfc_fmt formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_NV12MT_16X16,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V6_BIT | MFC_V7_BIT,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12MT,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V5_BIT,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV12M,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V5PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_NV21M,
+ .codec_mode = S5P_MFC_CODEC_NONE,
+ .type = MFC_FMT_RAW,
+ .num_planes = 2,
+ .versions = MFC_V6PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_H264,
+ .codec_mode = S5P_MFC_CODEC_H264_ENC,
+ .type = MFC_FMT_ENC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_MPEG4,
+ .codec_mode = S5P_MFC_CODEC_MPEG4_ENC,
+ .type = MFC_FMT_ENC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_H263,
+ .codec_mode = S5P_MFC_CODEC_H263_ENC,
+ .type = MFC_FMT_ENC,
+ .num_planes = 1,
+ .versions = MFC_V5PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_VP8,
+ .codec_mode = S5P_MFC_CODEC_VP8_ENC,
+ .type = MFC_FMT_ENC,
+ .num_planes = 1,
+ .versions = MFC_V7PLUS_BITS,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_HEVC,
+ .codec_mode = S5P_FIMV_CODEC_HEVC_ENC,
+ .type = MFC_FMT_ENC,
+ .num_planes = 1,
+ .versions = MFC_V10_BIT,
+ },
+};
+
+#define NUM_FORMATS ARRAY_SIZE(formats)
+static struct s5p_mfc_fmt *find_format(struct v4l2_format *f, unsigned int t)
+{
+ unsigned int i;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].fourcc == f->fmt.pix_mp.pixelformat &&
+ formats[i].type == t)
+ return &formats[i];
+ }
+ return NULL;
+}
+
+static struct mfc_control controls[] = {
+ {
+ .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 12,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ .maximum = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES,
+ .default_value = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 1,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 1900,
+ .maximum = (1 << 30) - 1,
+ .step = 1,
+ .default_value = 1900,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_PADDING,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Padding Control Enable",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_PADDING_YUV,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Padding Color YUV Value",
+ .minimum = 0,
+ .maximum = (1 << 25) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_BITRATE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 1,
+ .maximum = (1 << 30) - 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Rate Control Reaction Coeff.",
+ .minimum = 1,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Force frame type",
+ .minimum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED,
+ .maximum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_NOT_CODED,
+ .default_value = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME,
+ .type = V4L2_CTRL_TYPE_BUTTON,
+ .minimum = 0,
+ .maximum = 0,
+ .step = 0,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VBV_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MV_H_SEARCH_RANGE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Horizontal MV Search Range",
+ .minimum = 16,
+ .maximum = 128,
+ .step = 16,
+ .default_value = 32,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MV_V_SEARCH_RANGE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Vertical MV Search Range",
+ .minimum = 16,
+ .maximum = 128,
+ .step = 16,
+ .default_value = 32,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEADER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .maximum = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ .default_value = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Frame Skip Enable",
+ .minimum = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_DISABLED,
+ .maximum = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT,
+ .menu_skip_mask = 0,
+ .default_value = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_DISABLED,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .maximum = V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT,
+ .default_value = V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_DISABLED,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Fixed Target Bit Enable",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ .step = 1,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_B_FRAMES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .maximum = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH,
+ .default_value = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
+ .menu_skip_mask = ~(
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)
+ ),
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ .maximum = V4L2_MPEG_VIDEO_H264_LEVEL_4_0,
+ .default_value = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .maximum = V4L2_MPEG_VIDEO_MPEG4_LEVEL_5,
+ .default_value = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED,
+ .maximum = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY,
+ .default_value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = -6,
+ .maximum = 6,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = -6,
+ .maximum = 6,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ .maximum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC,
+ .default_value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "The Number of Ref. Pic for P",
+ .minimum = 1,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 51,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H263 I-Frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H263_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H263 Minimum QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H263_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H263 Maximum QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 31,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H263 P frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "H263 B frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "MPEG4 I-Frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "MPEG4 Minimum QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "MPEG4 Maximum QP value",
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 51,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "MPEG4 P frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "MPEG4 B frame QP value",
+ .minimum = 1,
+ .maximum = 31,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_DARK,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H264 Dark Reg Adaptive RC",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_SMOOTH,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H264 Smooth Reg Adaptive RC",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H264 Static Reg Adaptive RC",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_ACTIVITY,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "H264 Activity Reg Adaptive RC",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ .maximum = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED,
+ .default_value = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .maximum = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE,
+ .default_value = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_MPEG4_QPEL,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS,
+ .type = V4L2_CTRL_TYPE_INTEGER_MENU,
+ .maximum = V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS,
+ .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES,
+ .type = V4L2_CTRL_TYPE_INTEGER_MENU,
+ .maximum = V4L2_CID_MPEG_VIDEO_VPX_2_REF_FRAME,
+ .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_REF_FRAME,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 63,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 7,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV,
+ .maximum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_REF_PERIOD,
+ .default_value = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 127,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 11,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 10,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 127,
+ .step = 1,
+ .default_value = 10,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_VP8_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_VP8_PROFILE_0,
+ .maximum = V4L2_MPEG_VIDEO_VP8_PROFILE_3,
+ .default_value = V4L2_MPEG_VIDEO_VP8_PROFILE_0,
+ .menu_skip_mask = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "HEVC I Frame QP Value",
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "HEVC P Frame QP Value",
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_LEVEL,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_LEVEL_1,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_LEVEL_1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_TIER,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_FRAME_RATE_RESOLUTION,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 1,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_PARTITION_DEPTH,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_REF_NUMBER_FOR_PFRAMES,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 1,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_REFRESH_NONE,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_REFRESH_NONE,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_LOSSLESS_CU,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_WAVEFRONT,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_QP,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_TYPE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_B,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_P,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_B,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_LAYER,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 6,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 51,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_BR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = INT_MIN,
+ .maximum = INT_MAX,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_GENERAL_PB,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_TEMPORAL_ID,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_INTRA_PU_SPLIT,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 4,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_WITHOUT_STARTCODE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = (1 << 16) - 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = -6,
+ .maximum = 6,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = -6,
+ .maximum = 6,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_HEVC_SIZE_OF_LENGTH_FIELD,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .minimum = V4L2_MPEG_VIDEO_HEVC_SIZE_0,
+ .maximum = V4L2_MPEG_VIDEO_HEVC_SIZE_4,
+ .step = 1,
+ .default_value = V4L2_MPEG_VIDEO_HEVC_SIZE_0,
+ },
+ {
+ .id = V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_MIN_BUFFERS_FOR_OUTPUT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Minimum number of output bufs",
+ .minimum = 1,
+ .maximum = 32,
+ .step = 1,
+ .default_value = 1,
+ .is_volatile = 1,
+ },
+};
+
+#define NUM_CTRLS ARRAY_SIZE(controls)
+static const char * const *mfc51_get_menu(u32 id)
+{
+ static const char * const mfc51_video_frame_skip[] = {
+ "Disabled",
+ "Level Limit",
+ "VBV/CPB Limit",
+ NULL,
+ };
+ static const char * const mfc51_video_force_frame[] = {
+ "Disabled",
+ "I Frame",
+ "Not Coded",
+ NULL,
+ };
+ switch (id) {
+ case V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE:
+ return mfc51_video_frame_skip;
+ case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE:
+ return mfc51_video_force_frame;
+ }
+ return NULL;
+}
+
+static int s5p_mfc_ctx_ready(struct s5p_mfc_ctx *ctx)
+{
+ mfc_debug(2, "src=%d, dst=%d, state=%d\n",
+ ctx->src_queue_cnt, ctx->dst_queue_cnt, ctx->state);
+ /* context is ready to make header */
+ if (ctx->state == MFCINST_GOT_INST && ctx->dst_queue_cnt >= 1)
+ return 1;
+ /* context is ready to encode a frame */
+ if ((ctx->state == MFCINST_RUNNING ||
+ ctx->state == MFCINST_HEAD_PRODUCED) &&
+ ctx->src_queue_cnt >= 1 && ctx->dst_queue_cnt >= 1)
+ return 1;
+ /* context is ready to encode remaining frames */
+ if (ctx->state == MFCINST_FINISHING &&
+ ctx->dst_queue_cnt >= 1)
+ return 1;
+ mfc_debug(2, "ctx is not ready\n");
+ return 0;
+}
+
+static void cleanup_ref_queue(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_buf *mb_entry;
+
+ /* move buffers in ref queue to src queue */
+ while (!list_empty(&ctx->ref_queue)) {
+ mb_entry = list_entry((&ctx->ref_queue)->next,
+ struct s5p_mfc_buf, list);
+ list_del(&mb_entry->list);
+ ctx->ref_queue_cnt--;
+ list_add_tail(&mb_entry->list, &ctx->src_queue);
+ ctx->src_queue_cnt++;
+ }
+ mfc_debug(2, "enc src count: %d, enc ref count: %d\n",
+ ctx->src_queue_cnt, ctx->ref_queue_cnt);
+ INIT_LIST_HEAD(&ctx->ref_queue);
+ ctx->ref_queue_cnt = 0;
+}
+
+static int enc_pre_seq_start(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ unsigned long dst_addr;
+ unsigned int dst_size;
+
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+ s5p_mfc_hw_call(dev->mfc_ops, set_enc_stream_buffer, ctx, dst_addr,
+ dst_size);
+ return 0;
+}
+
+static int enc_post_seq_start(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_buf *dst_mb;
+ unsigned int enc_pb_count;
+
+ if (p->seq_hdr_mode == V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) {
+ if (!list_empty(&ctx->dst_queue)) {
+ dst_mb = list_entry(ctx->dst_queue.next,
+ struct s5p_mfc_buf, list);
+ list_del(&dst_mb->list);
+ ctx->dst_queue_cnt--;
+ vb2_set_plane_payload(&dst_mb->b->vb2_buf, 0,
+ s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size,
+ dev));
+ vb2_buffer_done(&dst_mb->b->vb2_buf,
+ VB2_BUF_STATE_DONE);
+ }
+ }
+
+ if (!IS_MFCV6_PLUS(dev)) {
+ ctx->state = MFCINST_RUNNING;
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ } else {
+ enc_pb_count = s5p_mfc_hw_call(dev->mfc_ops,
+ get_enc_dpb_count, dev);
+ if (ctx->pb_count < enc_pb_count)
+ ctx->pb_count = enc_pb_count;
+ if (FW_HAS_E_MIN_SCRATCH_BUF(dev)) {
+ ctx->scratch_buf_size = s5p_mfc_hw_call(dev->mfc_ops,
+ get_e_min_scratch_buf_size, dev);
+ ctx->bank1.size += ctx->scratch_buf_size;
+ }
+ ctx->state = MFCINST_HEAD_PRODUCED;
+ }
+
+ return 0;
+}
+
+static int enc_pre_frame_start(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ struct s5p_mfc_buf *src_mb;
+ unsigned long src_y_addr, src_c_addr, dst_addr;
+ unsigned int dst_size;
+
+ src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ src_y_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 0);
+ src_c_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 1);
+ s5p_mfc_hw_call(dev->mfc_ops, set_enc_frame_buffer, ctx,
+ src_y_addr, src_c_addr);
+
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+ s5p_mfc_hw_call(dev->mfc_ops, set_enc_stream_buffer, ctx, dst_addr,
+ dst_size);
+
+ return 0;
+}
+
+static int enc_post_frame_start(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *mb_entry;
+ unsigned long enc_y_addr = 0, enc_c_addr = 0;
+ unsigned long mb_y_addr, mb_c_addr;
+ int slice_type;
+ unsigned int strm_size;
+ bool src_ready;
+
+ slice_type = s5p_mfc_hw_call(dev->mfc_ops, get_enc_slice_type, dev);
+ strm_size = s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size, dev);
+ mfc_debug(2, "Encoded slice type: %d\n", slice_type);
+ mfc_debug(2, "Encoded stream size: %d\n", strm_size);
+ mfc_debug(2, "Display order: %d\n",
+ mfc_read(dev, S5P_FIMV_ENC_SI_PIC_CNT));
+ if (slice_type >= 0) {
+ s5p_mfc_hw_call(dev->mfc_ops, get_enc_frame_buffer, ctx,
+ &enc_y_addr, &enc_c_addr);
+ list_for_each_entry(mb_entry, &ctx->src_queue, list) {
+ mb_y_addr = vb2_dma_contig_plane_dma_addr(
+ &mb_entry->b->vb2_buf, 0);
+ mb_c_addr = vb2_dma_contig_plane_dma_addr(
+ &mb_entry->b->vb2_buf, 1);
+ if ((enc_y_addr == mb_y_addr) &&
+ (enc_c_addr == mb_c_addr)) {
+ list_del(&mb_entry->list);
+ ctx->src_queue_cnt--;
+ vb2_buffer_done(&mb_entry->b->vb2_buf,
+ VB2_BUF_STATE_DONE);
+ break;
+ }
+ }
+ list_for_each_entry(mb_entry, &ctx->ref_queue, list) {
+ mb_y_addr = vb2_dma_contig_plane_dma_addr(
+ &mb_entry->b->vb2_buf, 0);
+ mb_c_addr = vb2_dma_contig_plane_dma_addr(
+ &mb_entry->b->vb2_buf, 1);
+ if ((enc_y_addr == mb_y_addr) &&
+ (enc_c_addr == mb_c_addr)) {
+ list_del(&mb_entry->list);
+ ctx->ref_queue_cnt--;
+ vb2_buffer_done(&mb_entry->b->vb2_buf,
+ VB2_BUF_STATE_DONE);
+ break;
+ }
+ }
+ }
+ if (ctx->src_queue_cnt > 0 && (ctx->state == MFCINST_RUNNING ||
+ ctx->state == MFCINST_FINISHING)) {
+ mb_entry = list_entry(ctx->src_queue.next, struct s5p_mfc_buf,
+ list);
+ if (mb_entry->flags & MFC_BUF_FLAG_USED) {
+ list_del(&mb_entry->list);
+ ctx->src_queue_cnt--;
+ list_add_tail(&mb_entry->list, &ctx->ref_queue);
+ ctx->ref_queue_cnt++;
+ }
+ }
+ mfc_debug(2, "enc src count: %d, enc ref count: %d\n",
+ ctx->src_queue_cnt, ctx->ref_queue_cnt);
+ if ((ctx->dst_queue_cnt > 0) && (strm_size > 0)) {
+ mb_entry = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf,
+ list);
+ list_del(&mb_entry->list);
+ ctx->dst_queue_cnt--;
+ switch (slice_type) {
+ case S5P_FIMV_ENC_SI_SLICE_TYPE_I:
+ mb_entry->b->flags |= V4L2_BUF_FLAG_KEYFRAME;
+ break;
+ case S5P_FIMV_ENC_SI_SLICE_TYPE_P:
+ mb_entry->b->flags |= V4L2_BUF_FLAG_PFRAME;
+ break;
+ case S5P_FIMV_ENC_SI_SLICE_TYPE_B:
+ mb_entry->b->flags |= V4L2_BUF_FLAG_BFRAME;
+ break;
+ }
+ vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, strm_size);
+ vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ src_ready = true;
+ if (ctx->state == MFCINST_RUNNING && ctx->src_queue_cnt == 0)
+ src_ready = false;
+ if (ctx->state == MFCINST_FINISHING && ctx->ref_queue_cnt == 0)
+ src_ready = false;
+ if (!src_ready || ctx->dst_queue_cnt == 0)
+ clear_work_bit(ctx);
+
+ return 0;
+}
+
+static const struct s5p_mfc_codec_ops encoder_codec_ops = {
+ .pre_seq_start = enc_pre_seq_start,
+ .post_seq_start = enc_post_seq_start,
+ .pre_frame_start = enc_pre_frame_start,
+ .post_frame_start = enc_post_frame_start,
+};
+
+/* Query capabilities of the device */
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+
+ strscpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver));
+ strscpy(cap->card, dev->vfd_enc->name, sizeof(cap->card));
+ return 0;
+}
+
+static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *f,
+ bool out)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ int i, j = 0;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (out && formats[i].type != MFC_FMT_RAW)
+ continue;
+ else if (!out && formats[i].type != MFC_FMT_ENC)
+ continue;
+ else if ((dev->variant->version_bit & formats[i].versions) == 0)
+ continue;
+
+ if (j == f->index) {
+ f->pixelformat = formats[i].fourcc;
+ return 0;
+ }
+ ++j;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *pirv,
+ struct v4l2_fmtdesc *f)
+{
+ return vidioc_enum_fmt(file, f, false);
+}
+
+static int vidioc_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return vidioc_enum_fmt(file, f, true);
+}
+
+static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp;
+
+ mfc_debug(2, "f->type = %d ctx->state = %d\n", f->type, ctx->state);
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ /* This is run on output (encoder dest) */
+ pix_fmt_mp->width = 0;
+ pix_fmt_mp->height = 0;
+ pix_fmt_mp->field = V4L2_FIELD_NONE;
+ pix_fmt_mp->pixelformat = ctx->dst_fmt->fourcc;
+ pix_fmt_mp->num_planes = ctx->dst_fmt->num_planes;
+
+ pix_fmt_mp->plane_fmt[0].bytesperline = ctx->enc_dst_buf_size;
+ pix_fmt_mp->plane_fmt[0].sizeimage = ctx->enc_dst_buf_size;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ /* This is run on capture (encoder src) */
+ pix_fmt_mp->width = ctx->img_width;
+ pix_fmt_mp->height = ctx->img_height;
+
+ pix_fmt_mp->field = V4L2_FIELD_NONE;
+ pix_fmt_mp->pixelformat = ctx->src_fmt->fourcc;
+ pix_fmt_mp->num_planes = ctx->src_fmt->num_planes;
+
+ pix_fmt_mp->plane_fmt[0].bytesperline = ctx->buf_width;
+ pix_fmt_mp->plane_fmt[0].sizeimage = ctx->luma_size;
+ pix_fmt_mp->plane_fmt[1].bytesperline = ctx->buf_width;
+ pix_fmt_mp->plane_fmt[1].sizeimage = ctx->chroma_size;
+ } else {
+ mfc_err("invalid buf type\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_fmt *fmt;
+ struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp;
+
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ fmt = find_format(f, MFC_FMT_ENC);
+ if (!fmt) {
+ mfc_err("failed to try output format\n");
+ return -EINVAL;
+ }
+ if ((dev->variant->version_bit & fmt->versions) == 0) {
+ mfc_err("Unsupported format by this MFC version.\n");
+ return -EINVAL;
+ }
+
+ pix_fmt_mp->plane_fmt[0].bytesperline =
+ pix_fmt_mp->plane_fmt[0].sizeimage;
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ fmt = find_format(f, MFC_FMT_RAW);
+ if (!fmt) {
+ mfc_err("failed to try output format\n");
+ return -EINVAL;
+ }
+ if ((dev->variant->version_bit & fmt->versions) == 0) {
+ mfc_err("Unsupported format by this MFC version.\n");
+ return -EINVAL;
+ }
+
+ v4l_bound_align_image(&pix_fmt_mp->width, 8, 1920, 1,
+ &pix_fmt_mp->height, 4, 1080, 1, 0);
+ } else {
+ mfc_err("invalid buf type\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp;
+ int ret = 0;
+
+ ret = vidioc_try_fmt(file, priv, f);
+ if (ret)
+ return ret;
+ if (ctx->vq_src.streaming || ctx->vq_dst.streaming) {
+ v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__);
+ ret = -EBUSY;
+ goto out;
+ }
+ if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ /* dst_fmt is validated by call to vidioc_try_fmt */
+ ctx->dst_fmt = find_format(f, MFC_FMT_ENC);
+ ctx->state = MFCINST_INIT;
+ ctx->codec_mode = ctx->dst_fmt->codec_mode;
+ ctx->enc_dst_buf_size = pix_fmt_mp->plane_fmt[0].sizeimage;
+ pix_fmt_mp->plane_fmt[0].bytesperline = 0;
+ ctx->dst_bufs_cnt = 0;
+ ctx->capture_state = QUEUE_FREE;
+ ret = s5p_mfc_open_mfc_inst(dev, ctx);
+ } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ /* src_fmt is validated by call to vidioc_try_fmt */
+ ctx->src_fmt = find_format(f, MFC_FMT_RAW);
+ ctx->img_width = pix_fmt_mp->width;
+ ctx->img_height = pix_fmt_mp->height;
+ mfc_debug(2, "codec number: %d\n", ctx->src_fmt->codec_mode);
+ mfc_debug(2, "fmt - w: %d, h: %d, ctx - w: %d, h: %d\n",
+ pix_fmt_mp->width, pix_fmt_mp->height,
+ ctx->img_width, ctx->img_height);
+
+ s5p_mfc_hw_call(dev->mfc_ops, enc_calc_src_size, ctx);
+ pix_fmt_mp->plane_fmt[0].sizeimage = ctx->luma_size;
+ pix_fmt_mp->plane_fmt[0].bytesperline = ctx->buf_width;
+ pix_fmt_mp->plane_fmt[1].sizeimage = ctx->chroma_size;
+ pix_fmt_mp->plane_fmt[1].bytesperline = ctx->buf_width;
+
+ ctx->src_bufs_cnt = 0;
+ ctx->output_state = QUEUE_FREE;
+ } else {
+ mfc_err("invalid buf type\n");
+ ret = -EINVAL;
+ }
+out:
+ mfc_debug_leave();
+ return ret;
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *reqbufs)
+{
+ struct s5p_mfc_dev *dev = video_drvdata(file);
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret = 0;
+
+ /* if memory is not mmp or userptr return error */
+ if ((reqbufs->memory != V4L2_MEMORY_MMAP) &&
+ (reqbufs->memory != V4L2_MEMORY_USERPTR))
+ return -EINVAL;
+ if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (reqbufs->count == 0) {
+ mfc_debug(2, "Freeing buffers\n");
+ ret = vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers,
+ ctx);
+ ctx->capture_state = QUEUE_FREE;
+ return ret;
+ }
+ if (ctx->capture_state != QUEUE_FREE) {
+ mfc_err("invalid capture state: %d\n",
+ ctx->capture_state);
+ return -EINVAL;
+ }
+ ret = vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ if (ret != 0) {
+ mfc_err("error in vb2_reqbufs() for E(D)\n");
+ return ret;
+ }
+ ctx->capture_state = QUEUE_BUFS_REQUESTED;
+
+ ret = s5p_mfc_hw_call(ctx->dev->mfc_ops,
+ alloc_codec_buffers, ctx);
+ if (ret) {
+ mfc_err("Failed to allocate encoding buffers\n");
+ reqbufs->count = 0;
+ ret = vb2_reqbufs(&ctx->vq_dst, reqbufs);
+ return -ENOMEM;
+ }
+ } else if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ if (reqbufs->count == 0) {
+ mfc_debug(2, "Freeing buffers\n");
+ ret = vb2_reqbufs(&ctx->vq_src, reqbufs);
+ s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers,
+ ctx);
+ ctx->output_state = QUEUE_FREE;
+ return ret;
+ }
+ if (ctx->output_state != QUEUE_FREE) {
+ mfc_err("invalid output state: %d\n",
+ ctx->output_state);
+ return -EINVAL;
+ }
+
+ if (IS_MFCV6_PLUS(dev)) {
+ /* Check for min encoder buffers */
+ if (ctx->pb_count &&
+ (reqbufs->count < ctx->pb_count)) {
+ reqbufs->count = ctx->pb_count;
+ mfc_debug(2, "Minimum %d output buffers needed\n",
+ ctx->pb_count);
+ } else {
+ ctx->pb_count = reqbufs->count;
+ }
+ }
+
+ ret = vb2_reqbufs(&ctx->vq_src, reqbufs);
+ if (ret != 0) {
+ mfc_err("error in vb2_reqbufs() for E(S)\n");
+ return ret;
+ }
+ ctx->output_state = QUEUE_BUFS_REQUESTED;
+ } else {
+ mfc_err("invalid buf type\n");
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static int vidioc_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *buf)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret = 0;
+
+ /* if memory is not mmp or userptr return error */
+ if ((buf->memory != V4L2_MEMORY_MMAP) &&
+ (buf->memory != V4L2_MEMORY_USERPTR))
+ return -EINVAL;
+ if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (ctx->state != MFCINST_GOT_INST) {
+ mfc_err("invalid context state: %d\n", ctx->state);
+ return -EINVAL;
+ }
+ ret = vb2_querybuf(&ctx->vq_dst, buf);
+ if (ret != 0) {
+ mfc_err("error in vb2_querybuf() for E(D)\n");
+ return ret;
+ }
+ buf->m.planes[0].m.mem_offset += DST_QUEUE_OFF_BASE;
+ } else if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = vb2_querybuf(&ctx->vq_src, buf);
+ if (ret != 0) {
+ mfc_err("error in vb2_querybuf() for E(S)\n");
+ return ret;
+ }
+ } else {
+ mfc_err("invalid buf type\n");
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/* Queue a buffer */
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (ctx->state == MFCINST_ERROR) {
+ mfc_err("Call on QBUF after unrecoverable error\n");
+ return -EIO;
+ }
+ if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ if (ctx->state == MFCINST_FINISHING) {
+ mfc_err("Call on QBUF after EOS command\n");
+ return -EIO;
+ }
+ return vb2_qbuf(&ctx->vq_src, NULL, buf);
+ } else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ return vb2_qbuf(&ctx->vq_dst, NULL, buf);
+ }
+ return -EINVAL;
+}
+
+/* Dequeue a buffer */
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+ const struct v4l2_event ev = {
+ .type = V4L2_EVENT_EOS
+ };
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ int ret;
+
+ if (ctx->state == MFCINST_ERROR) {
+ mfc_err_limited("Call on DQBUF after unrecoverable error\n");
+ return -EIO;
+ }
+ if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = vb2_dqbuf(&ctx->vq_src, buf, file->f_flags & O_NONBLOCK);
+ } else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ ret = vb2_dqbuf(&ctx->vq_dst, buf, file->f_flags & O_NONBLOCK);
+ if (ret == 0 && ctx->state == MFCINST_FINISHED
+ && list_empty(&ctx->vq_dst.done_list))
+ v4l2_event_queue_fh(&ctx->fh, &ev);
+ } else {
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Export DMA buffer */
+static int vidioc_expbuf(struct file *file, void *priv,
+ struct v4l2_exportbuffer *eb)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (eb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_expbuf(&ctx->vq_src, eb);
+ if (eb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_expbuf(&ctx->vq_dst, eb);
+ return -EINVAL;
+}
+
+/* Stream on */
+static int vidioc_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_streamon(&ctx->vq_src, type);
+ else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_streamon(&ctx->vq_dst, type);
+ return -EINVAL;
+}
+
+/* Stream off, which equals to a pause */
+static int vidioc_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return vb2_streamoff(&ctx->vq_src, type);
+ else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return vb2_streamoff(&ctx->vq_dst, type);
+ return -EINVAL;
+}
+
+static inline int h264_level(enum v4l2_mpeg_video_h264_level lvl)
+{
+ static unsigned int t[V4L2_MPEG_VIDEO_H264_LEVEL_4_0 + 1] = {
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_1_0 */ 10,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_1B */ 9,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_1_1 */ 11,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_1_2 */ 12,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_1_3 */ 13,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_2_0 */ 20,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_2_1 */ 21,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_2_2 */ 22,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_3_0 */ 30,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_3_1 */ 31,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_3_2 */ 32,
+ /* V4L2_MPEG_VIDEO_H264_LEVEL_4_0 */ 40,
+ };
+ return t[lvl];
+}
+
+static inline int mpeg4_level(enum v4l2_mpeg_video_mpeg4_level lvl)
+{
+ static unsigned int t[V4L2_MPEG_VIDEO_MPEG4_LEVEL_5 + 1] = {
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_0 */ 0,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B */ 9,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_1 */ 1,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_2 */ 2,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_3 */ 3,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_3B */ 7,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_4 */ 4,
+ /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_5 */ 5,
+ };
+ return t[lvl];
+}
+
+static inline int hevc_level(enum v4l2_mpeg_video_hevc_level lvl)
+{
+ static unsigned int t[] = {
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_1 */ 10,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_2 */ 20,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1 */ 21,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_3 */ 30,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1 */ 31,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_4 */ 40,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1 */ 41,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5 */ 50,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1 */ 51,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2 */ 52,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6 */ 60,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1 */ 61,
+ /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2 */ 62,
+ };
+ return t[lvl];
+}
+
+static inline int vui_sar_idc(enum v4l2_mpeg_video_h264_vui_sar_idc sar)
+{
+ static unsigned int t[V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED + 1] = {
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED */ 0,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1 */ 1,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_12x11 */ 2,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_10x11 */ 3,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_16x11 */ 4,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_40x33 */ 5,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_24x11 */ 6,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_20x11 */ 7,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_32x11 */ 8,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_80x33 */ 9,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_18x11 */ 10,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_15x11 */ 11,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_64x33 */ 12,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_160x99 */ 13,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_4x3 */ 14,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_3x2 */ 15,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_2x1 */ 16,
+ /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED */ 255,
+ };
+ return t[sar];
+}
+
+/*
+ * Update range of all HEVC quantization parameter controls that depend on the
+ * V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP controls.
+ */
+static void __enc_update_hevc_qp_ctrls_range(struct s5p_mfc_ctx *ctx,
+ int min, int max)
+{
+ static const int __hevc_qp_ctrls[] = {
+ V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP,
+ V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP,
+ };
+ struct v4l2_ctrl *ctrl = NULL;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(__hevc_qp_ctrls); i++) {
+ for (j = 0; j < ARRAY_SIZE(ctx->ctrls); j++) {
+ if (ctx->ctrls[j]->id == __hevc_qp_ctrls[i]) {
+ ctrl = ctx->ctrls[j];
+ break;
+ }
+ }
+ if (WARN_ON(!ctrl))
+ break;
+
+ __v4l2_ctrl_modify_range(ctrl, min, max, ctrl->step, min);
+ }
+}
+
+static int s5p_mfc_enc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ p->gop_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE:
+ p->slice_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB:
+ p->slice_mb = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES:
+ p->slice_bit = ctrl->val * 8;
+ break;
+ case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB:
+ p->intra_refresh_mb = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_PADDING:
+ p->pad = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_PADDING_YUV:
+ p->pad_luma = (ctrl->val >> 16) & 0xff;
+ p->pad_cb = (ctrl->val >> 8) & 0xff;
+ p->pad_cr = (ctrl->val >> 0) & 0xff;
+ break;
+ case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE:
+ p->rc_frame = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_BITRATE:
+ p->rc_bitrate = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF:
+ p->rc_reaction_coeff = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE:
+ ctx->force_frame_type = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME:
+ ctx->force_frame_type =
+ V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_I_FRAME;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VBV_SIZE:
+ p->vbv_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MV_H_SEARCH_RANGE:
+ p->mv_h_range = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MV_V_SEARCH_RANGE:
+ p->mv_v_range = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE:
+ p->codec.h264.cpb_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEADER_MODE:
+ p->seq_hdr_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE:
+ case V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE:
+ p->frame_skip_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT:
+ p->fixed_target_bit = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_B_FRAMES:
+ p->num_b_frame = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+ p->codec.h264.profile =
+ S5P_FIMV_ENC_PROFILE_H264_MAIN;
+ break;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+ p->codec.h264.profile =
+ S5P_FIMV_ENC_PROFILE_H264_HIGH;
+ break;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+ p->codec.h264.profile =
+ S5P_FIMV_ENC_PROFILE_H264_BASELINE;
+ break;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
+ if (IS_MFCV6_PLUS(dev))
+ p->codec.h264.profile =
+ S5P_FIMV_ENC_PROFILE_H264_CONSTRAINED_BASELINE;
+ else
+ ret = -EINVAL;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+ p->codec.h264.level_v4l2 = ctrl->val;
+ p->codec.h264.level = h264_level(ctrl->val);
+ if (p->codec.h264.level < 0) {
+ mfc_err("Level number is wrong\n");
+ ret = p->codec.h264.level;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL:
+ p->codec.mpeg4.level_v4l2 = ctrl->val;
+ p->codec.mpeg4.level = mpeg4_level(ctrl->val);
+ if (p->codec.mpeg4.level < 0) {
+ mfc_err("Level number is wrong\n");
+ ret = p->codec.mpeg4.level;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE:
+ p->codec.h264.loop_filter_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA:
+ p->codec.h264.loop_filter_alpha = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA:
+ p->codec.h264.loop_filter_beta = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE:
+ p->codec.h264.entropy_mode = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P:
+ p->codec.h264.num_ref_pic_4p = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM:
+ p->codec.h264._8x8_transform = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE:
+ p->rc_mb = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP:
+ p->codec.h264.rc_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+ p->codec.h264.rc_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_MAX_QP:
+ p->codec.h264.rc_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP:
+ p->codec.h264.rc_p_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP:
+ p->codec.h264.rc_b_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP:
+ case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP:
+ p->codec.mpeg4.rc_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP:
+ case V4L2_CID_MPEG_VIDEO_H263_MIN_QP:
+ p->codec.mpeg4.rc_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP:
+ case V4L2_CID_MPEG_VIDEO_H263_MAX_QP:
+ p->codec.mpeg4.rc_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP:
+ case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP:
+ p->codec.mpeg4.rc_p_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP:
+ case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP:
+ p->codec.mpeg4.rc_b_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_DARK:
+ p->codec.h264.rc_mb_dark = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_SMOOTH:
+ p->codec.h264.rc_mb_smooth = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC:
+ p->codec.h264.rc_mb_static = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_ACTIVITY:
+ p->codec.h264.rc_mb_activity = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE:
+ p->codec.h264.vui_sar = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC:
+ p->codec.h264.vui_sar_idc = vui_sar_idc(ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH:
+ p->codec.h264.vui_ext_sar_width = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT:
+ p->codec.h264.vui_ext_sar_height = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE:
+ p->codec.h264.open_gop = !ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
+ p->codec.h264.open_gop_size = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE:
+ p->codec.mpeg4.profile =
+ S5P_FIMV_ENC_PROFILE_MPEG4_SIMPLE;
+ break;
+ case V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE:
+ p->codec.mpeg4.profile =
+ S5P_FIMV_ENC_PROFILE_MPEG4_ADVANCED_SIMPLE;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL:
+ p->codec.mpeg4.quarter_pixel = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS:
+ p->codec.vp8.num_partitions = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4:
+ p->codec.vp8.imd_4x4 = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES:
+ p->codec.vp8.num_ref = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL:
+ p->codec.vp8.filter_level = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS:
+ p->codec.vp8.filter_sharpness = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD:
+ p->codec.vp8.golden_frame_ref_period = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL:
+ p->codec.vp8.golden_frame_sel = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP:
+ p->codec.vp8.rc_min_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP:
+ p->codec.vp8.rc_max_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP:
+ p->codec.vp8.rc_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP:
+ p->codec.vp8.rc_p_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VP8_PROFILE:
+ p->codec.vp8.profile = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP:
+ p->codec.hevc.rc_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP:
+ p->codec.hevc.rc_p_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP:
+ p->codec.hevc.rc_b_frame_qp = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_FRAME_RATE_RESOLUTION:
+ p->codec.hevc.rc_framerate = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP:
+ p->codec.hevc.rc_min_qp = ctrl->val;
+ __enc_update_hevc_qp_ctrls_range(ctx, ctrl->val,
+ p->codec.hevc.rc_max_qp);
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP:
+ p->codec.hevc.rc_max_qp = ctrl->val;
+ __enc_update_hevc_qp_ctrls_range(ctx, p->codec.hevc.rc_min_qp,
+ ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL:
+ p->codec.hevc.level_v4l2 = ctrl->val;
+ p->codec.hevc.level = hevc_level(ctrl->val);
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE:
+ switch (ctrl->val) {
+ case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN:
+ p->codec.hevc.profile =
+ V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN;
+ break;
+ case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE:
+ p->codec.hevc.profile =
+ V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_TIER:
+ p->codec.hevc.tier = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_MAX_PARTITION_DEPTH:
+ p->codec.hevc.max_partition_depth = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_REF_NUMBER_FOR_PFRAMES:
+ p->codec.hevc.num_refs_for_p = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE:
+ p->codec.hevc.refreshtype = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED:
+ p->codec.hevc.const_intra_period_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_LOSSLESS_CU:
+ p->codec.hevc.lossless_cu_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_WAVEFRONT:
+ p->codec.hevc.wavefront_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE:
+ p->codec.hevc.loopfilter = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_QP:
+ p->codec.hevc.hier_qp_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_TYPE:
+ p->codec.hevc.hier_qp_type = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_LAYER:
+ p->codec.hevc.num_hier_layer = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP:
+ p->codec.hevc.hier_qp_layer[0] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP:
+ p->codec.hevc.hier_qp_layer[1] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP:
+ p->codec.hevc.hier_qp_layer[2] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP:
+ p->codec.hevc.hier_qp_layer[3] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP:
+ p->codec.hevc.hier_qp_layer[4] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP:
+ p->codec.hevc.hier_qp_layer[5] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP:
+ p->codec.hevc.hier_qp_layer[6] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_BR:
+ p->codec.hevc.hier_bit_layer[0] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_BR:
+ p->codec.hevc.hier_bit_layer[1] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_BR:
+ p->codec.hevc.hier_bit_layer[2] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_BR:
+ p->codec.hevc.hier_bit_layer[3] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_BR:
+ p->codec.hevc.hier_bit_layer[4] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_BR:
+ p->codec.hevc.hier_bit_layer[5] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_BR:
+ p->codec.hevc.hier_bit_layer[6] = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_GENERAL_PB:
+ p->codec.hevc.general_pb_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_TEMPORAL_ID:
+ p->codec.hevc.temporal_id_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING:
+ p->codec.hevc.strong_intra_smooth = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_INTRA_PU_SPLIT:
+ p->codec.hevc.intra_pu_split_disable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION:
+ p->codec.hevc.tmv_prediction_disable = !ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1:
+ p->codec.hevc.max_num_merge_mv = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_WITHOUT_STARTCODE:
+ p->codec.hevc.encoding_nostartcode_enable = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD:
+ p->codec.hevc.refreshperiod = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2:
+ p->codec.hevc.lf_beta_offset_div2 = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2:
+ p->codec.hevc.lf_tc_offset_div2 = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEVC_SIZE_OF_LENGTH_FIELD:
+ p->codec.hevc.size_of_length_field = ctrl->val;
+ break;
+ case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
+ p->codec.hevc.prepend_sps_pps_to_idr = ctrl->val;
+ break;
+ default:
+ v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n",
+ ctrl->id, ctrl->val);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int s5p_mfc_enc_g_v_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ switch (ctrl->id) {
+ case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
+ if (ctx->state >= MFCINST_HEAD_PARSED &&
+ ctx->state < MFCINST_ABORT) {
+ ctrl->val = ctx->pb_count;
+ break;
+ } else if (ctx->state != MFCINST_INIT) {
+ v4l2_err(&dev->v4l2_dev, "Encoding not initialised\n");
+ return -EINVAL;
+ }
+ /* Should wait for the header to be produced */
+ s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_SEQ_DONE_RET, 0);
+ if (ctx->state >= MFCINST_HEAD_PARSED &&
+ ctx->state < MFCINST_ABORT) {
+ ctrl->val = ctx->pb_count;
+ } else {
+ v4l2_err(&dev->v4l2_dev, "Encoding not initialised\n");
+ return -EINVAL;
+ }
+ break;
+ }
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops s5p_mfc_enc_ctrl_ops = {
+ .s_ctrl = s5p_mfc_enc_s_ctrl,
+ .g_volatile_ctrl = s5p_mfc_enc_g_v_ctrl,
+};
+
+static int vidioc_s_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ctx->enc_params.rc_framerate_num =
+ a->parm.output.timeperframe.denominator;
+ ctx->enc_params.rc_framerate_denom =
+ a->parm.output.timeperframe.numerator;
+ } else {
+ mfc_err("Setting FPS is only possible for the output queue\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+
+ if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+ a->parm.output.timeperframe.denominator =
+ ctx->enc_params.rc_framerate_num;
+ a->parm.output.timeperframe.numerator =
+ ctx->enc_params.rc_framerate_denom;
+ } else {
+ mfc_err("Setting FPS is only possible for the output queue\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_encoder_cmd(struct file *file, void *priv,
+ struct v4l2_encoder_cmd *cmd)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *buf;
+ unsigned long flags;
+
+ switch (cmd->cmd) {
+ case V4L2_ENC_CMD_STOP:
+ if (cmd->flags != 0)
+ return -EINVAL;
+
+ if (!ctx->vq_src.streaming)
+ return -EINVAL;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ if (list_empty(&ctx->src_queue)) {
+ mfc_debug(2, "EOS: empty src queue, entering finishing state\n");
+ ctx->state = MFCINST_FINISHING;
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+ } else {
+ mfc_debug(2, "EOS: marking last buffer of stream\n");
+ buf = list_entry(ctx->src_queue.prev,
+ struct s5p_mfc_buf, list);
+ if (buf->flags & MFC_BUF_FLAG_USED)
+ ctx->state = MFCINST_FINISHING;
+ else
+ buf->flags |= MFC_BUF_FLAG_EOS;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ }
+ break;
+ default:
+ return -EINVAL;
+
+ }
+ return 0;
+}
+
+static int vidioc_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;
+ }
+}
+
+static const struct v4l2_ioctl_ops s5p_mfc_enc_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out,
+ .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt,
+ .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt,
+ .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt,
+ .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt,
+ .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt,
+ .vidioc_reqbufs = vidioc_reqbufs,
+ .vidioc_querybuf = vidioc_querybuf,
+ .vidioc_qbuf = vidioc_qbuf,
+ .vidioc_dqbuf = vidioc_dqbuf,
+ .vidioc_expbuf = vidioc_expbuf,
+ .vidioc_streamon = vidioc_streamon,
+ .vidioc_streamoff = vidioc_streamoff,
+ .vidioc_s_parm = vidioc_s_parm,
+ .vidioc_g_parm = vidioc_g_parm,
+ .vidioc_encoder_cmd = vidioc_encoder_cmd,
+ .vidioc_subscribe_event = vidioc_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int check_vb_with_fmt(struct s5p_mfc_fmt *fmt, struct vb2_buffer *vb)
+{
+ int i;
+
+ if (!fmt)
+ return -EINVAL;
+ if (fmt->num_planes != vb->num_planes) {
+ mfc_err("invalid plane number for the format\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < fmt->num_planes; i++) {
+ dma_addr_t dma = vb2_dma_contig_plane_dma_addr(vb, i);
+ if (!dma) {
+ mfc_err("failed to get plane cookie\n");
+ return -EINVAL;
+ }
+ mfc_debug(2, "index: %d, plane[%d] cookie: %pad\n",
+ vb->index, i, &dma);
+ }
+ return 0;
+}
+
+static int s5p_mfc_queue_setup(struct vb2_queue *vq,
+ unsigned int *buf_count, unsigned int *plane_count,
+ unsigned int psize[], struct device *alloc_devs[])
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (ctx->state != MFCINST_GOT_INST) {
+ mfc_err("invalid state: %d\n", ctx->state);
+ return -EINVAL;
+ }
+
+ if (ctx->dst_fmt)
+ *plane_count = ctx->dst_fmt->num_planes;
+ else
+ *plane_count = MFC_ENC_CAP_PLANE_COUNT;
+ if (*buf_count < 1)
+ *buf_count = 1;
+ if (*buf_count > MFC_MAX_BUFFERS)
+ *buf_count = MFC_MAX_BUFFERS;
+ psize[0] = ctx->enc_dst_buf_size;
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX];
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ if (ctx->src_fmt)
+ *plane_count = ctx->src_fmt->num_planes;
+ else
+ *plane_count = MFC_ENC_OUT_PLANE_COUNT;
+
+ if (*buf_count < 1)
+ *buf_count = 1;
+ if (*buf_count > MFC_MAX_BUFFERS)
+ *buf_count = MFC_MAX_BUFFERS;
+
+ psize[0] = ctx->luma_size;
+ psize[1] = ctx->chroma_size;
+
+ if (IS_MFCV6_PLUS(dev)) {
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX];
+ alloc_devs[1] = ctx->dev->mem_dev[BANK_L_CTX];
+ } else {
+ alloc_devs[0] = ctx->dev->mem_dev[BANK_R_CTX];
+ alloc_devs[1] = ctx->dev->mem_dev[BANK_R_CTX];
+ }
+ } else {
+ mfc_err("invalid queue type: %d\n", vq->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_buf_init(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ unsigned int i;
+ int ret;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ ret = check_vb_with_fmt(ctx->dst_fmt, vb);
+ if (ret < 0)
+ return ret;
+ i = vb->index;
+ ctx->dst_bufs[i].b = vbuf;
+ ctx->dst_bufs[i].cookie.stream =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ ctx->dst_bufs_cnt++;
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = check_vb_with_fmt(ctx->src_fmt, vb);
+ if (ret < 0)
+ return ret;
+ i = vb->index;
+ ctx->src_bufs[i].b = vbuf;
+ ctx->src_bufs[i].cookie.raw.luma =
+ vb2_dma_contig_plane_dma_addr(vb, 0);
+ ctx->src_bufs[i].cookie.raw.chroma =
+ vb2_dma_contig_plane_dma_addr(vb, 1);
+ ctx->src_bufs_cnt++;
+ } else {
+ mfc_err("invalid queue type: %d\n", vq->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ int ret;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ ret = check_vb_with_fmt(ctx->dst_fmt, vb);
+ if (ret < 0)
+ return ret;
+ mfc_debug(2, "plane size: %ld, dst size: %zu\n",
+ vb2_plane_size(vb, 0), ctx->enc_dst_buf_size);
+ if (vb2_plane_size(vb, 0) < ctx->enc_dst_buf_size) {
+ mfc_err("plane size is too small for capture\n");
+ return -EINVAL;
+ }
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ ret = check_vb_with_fmt(ctx->src_fmt, vb);
+ if (ret < 0)
+ return ret;
+ mfc_debug(2, "plane size: %ld, luma size: %d\n",
+ vb2_plane_size(vb, 0), ctx->luma_size);
+ mfc_debug(2, "plane size: %ld, chroma size: %d\n",
+ vb2_plane_size(vb, 1), ctx->chroma_size);
+ if (vb2_plane_size(vb, 0) < ctx->luma_size ||
+ vb2_plane_size(vb, 1) < ctx->chroma_size) {
+ mfc_err("plane size is too small for output\n");
+ return -EINVAL;
+ }
+ } else {
+ mfc_err("invalid queue type: %d\n", vq->type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ if (IS_MFCV6_PLUS(dev) &&
+ (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) {
+
+ if ((ctx->state == MFCINST_GOT_INST) &&
+ (dev->curr_ctx == ctx->num) && dev->hw_lock) {
+ s5p_mfc_wait_for_done_ctx(ctx,
+ S5P_MFC_R2H_CMD_SEQ_DONE_RET,
+ 0);
+ }
+
+ if (ctx->src_bufs_cnt < ctx->pb_count) {
+ mfc_err("Need minimum %d OUTPUT buffers\n",
+ ctx->pb_count);
+ return -ENOBUFS;
+ }
+ }
+
+ /* If context is ready then dev = work->data;schedule it to run */
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+
+ return 0;
+}
+
+static void s5p_mfc_stop_streaming(struct vb2_queue *q)
+{
+ unsigned long flags;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ if ((ctx->state == MFCINST_FINISHING ||
+ ctx->state == MFCINST_RUNNING) &&
+ dev->curr_ctx == ctx->num && dev->hw_lock) {
+ ctx->state = MFCINST_ABORT;
+ s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_FRAME_DONE_RET,
+ 0);
+ }
+ ctx->state = MFCINST_FINISHED;
+ spin_lock_irqsave(&dev->irqlock, flags);
+ if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
+ INIT_LIST_HEAD(&ctx->dst_queue);
+ ctx->dst_queue_cnt = 0;
+ }
+ if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ cleanup_ref_queue(ctx);
+ s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
+ INIT_LIST_HEAD(&ctx->src_queue);
+ ctx->src_queue_cnt = 0;
+ }
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+}
+
+static void s5p_mfc_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv);
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned long flags;
+ struct s5p_mfc_buf *mfc_buf;
+
+ if (ctx->state == MFCINST_ERROR) {
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ cleanup_ref_queue(ctx);
+ return;
+ }
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ mfc_buf = &ctx->dst_bufs[vb->index];
+ mfc_buf->flags &= ~MFC_BUF_FLAG_USED;
+ /* Mark destination as available for use by MFC */
+ spin_lock_irqsave(&dev->irqlock, flags);
+ list_add_tail(&mfc_buf->list, &ctx->dst_queue);
+ ctx->dst_queue_cnt++;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+ mfc_buf = &ctx->src_bufs[vb->index];
+ mfc_buf->flags &= ~MFC_BUF_FLAG_USED;
+ spin_lock_irqsave(&dev->irqlock, flags);
+ list_add_tail(&mfc_buf->list, &ctx->src_queue);
+ ctx->src_queue_cnt++;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
+ } else {
+ mfc_err("unsupported buffer type (%d)\n", vq->type);
+ }
+ if (s5p_mfc_ctx_ready(ctx))
+ set_work_bit_irqsave(ctx);
+ s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
+}
+
+static struct vb2_ops s5p_mfc_enc_qops = {
+ .queue_setup = s5p_mfc_queue_setup,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .buf_init = s5p_mfc_buf_init,
+ .buf_prepare = s5p_mfc_buf_prepare,
+ .start_streaming = s5p_mfc_start_streaming,
+ .stop_streaming = s5p_mfc_stop_streaming,
+ .buf_queue = s5p_mfc_buf_queue,
+};
+
+const struct s5p_mfc_codec_ops *get_enc_codec_ops(void)
+{
+ return &encoder_codec_ops;
+}
+
+struct vb2_ops *get_enc_queue_ops(void)
+{
+ return &s5p_mfc_enc_qops;
+}
+
+const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void)
+{
+ return &s5p_mfc_enc_ioctl_ops;
+}
+
+#define IS_MFC51_PRIV(x) ((V4L2_CTRL_ID2WHICH(x) == V4L2_CTRL_CLASS_CODEC) \
+ && V4L2_CTRL_DRIVER_PRIV(x))
+
+int s5p_mfc_enc_ctrls_setup(struct s5p_mfc_ctx *ctx)
+{
+ struct v4l2_ctrl_config cfg;
+ int i;
+
+ v4l2_ctrl_handler_init(&ctx->ctrl_handler, NUM_CTRLS);
+ if (ctx->ctrl_handler.error) {
+ mfc_err("v4l2_ctrl_handler_init failed\n");
+ return ctx->ctrl_handler.error;
+ }
+ for (i = 0; i < NUM_CTRLS; i++) {
+ if (IS_MFC51_PRIV(controls[i].id)) {
+ memset(&cfg, 0, sizeof(struct v4l2_ctrl_config));
+ cfg.ops = &s5p_mfc_enc_ctrl_ops;
+ cfg.id = controls[i].id;
+ cfg.min = controls[i].minimum;
+ cfg.max = controls[i].maximum;
+ cfg.def = controls[i].default_value;
+ cfg.name = controls[i].name;
+ cfg.type = controls[i].type;
+ cfg.flags = 0;
+
+ if (cfg.type == V4L2_CTRL_TYPE_MENU) {
+ cfg.step = 0;
+ cfg.menu_skip_mask = controls[i].menu_skip_mask;
+ cfg.qmenu = mfc51_get_menu(cfg.id);
+ } else {
+ cfg.step = controls[i].step;
+ cfg.menu_skip_mask = 0;
+ }
+ ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler,
+ &cfg, NULL);
+ } else {
+ if ((controls[i].type == V4L2_CTRL_TYPE_MENU) ||
+ (controls[i].type ==
+ V4L2_CTRL_TYPE_INTEGER_MENU)) {
+ ctx->ctrls[i] = v4l2_ctrl_new_std_menu(
+ &ctx->ctrl_handler,
+ &s5p_mfc_enc_ctrl_ops, controls[i].id,
+ controls[i].maximum, 0,
+ controls[i].default_value);
+ } else {
+ ctx->ctrls[i] = v4l2_ctrl_new_std(
+ &ctx->ctrl_handler,
+ &s5p_mfc_enc_ctrl_ops, controls[i].id,
+ controls[i].minimum,
+ controls[i].maximum, controls[i].step,
+ controls[i].default_value);
+ }
+ }
+ if (ctx->ctrl_handler.error) {
+ mfc_err("Adding control (%d) failed\n", i);
+ return ctx->ctrl_handler.error;
+ }
+ if (controls[i].is_volatile && ctx->ctrls[i])
+ ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_VOLATILE;
+ }
+ v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+ return 0;
+}
+
+void s5p_mfc_enc_ctrls_delete(struct s5p_mfc_ctx *ctx)
+{
+ int i;
+
+ v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+ for (i = 0; i < NUM_CTRLS; i++)
+ ctx->ctrls[i] = NULL;
+}
+
+void s5p_mfc_enc_init(struct s5p_mfc_ctx *ctx)
+{
+ struct v4l2_format f;
+ f.fmt.pix_mp.pixelformat = DEF_SRC_FMT_ENC;
+ ctx->src_fmt = find_format(&f, MFC_FMT_RAW);
+ f.fmt.pix_mp.pixelformat = DEF_DST_FMT_ENC;
+ ctx->dst_fmt = find_format(&f, MFC_FMT_ENC);
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h
new file mode 100644
index 0000000000..3f1b1a037a
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_ENC_H_
+#define S5P_MFC_ENC_H_
+
+const struct s5p_mfc_codec_ops *get_enc_codec_ops(void);
+struct vb2_ops *get_enc_queue_ops(void);
+const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void);
+struct s5p_mfc_fmt *get_enc_def_fmt(bool src);
+int s5p_mfc_enc_ctrls_setup(struct s5p_mfc_ctx *ctx);
+void s5p_mfc_enc_ctrls_delete(struct s5p_mfc_ctx *ctx);
+void s5p_mfc_enc_init(struct s5p_mfc_ctx *ctx);
+
+#endif /* S5P_MFC_ENC_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c
new file mode 100644
index 0000000000..0a38f6d70e
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/platform/samsung/mfc5/s5p_mfc_intr.c
+ *
+ * C file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * This file contains functions used to wait for command completion.
+ *
+ * Kamil Debski, Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_intr.h"
+
+int s5p_mfc_wait_for_done_dev(struct s5p_mfc_dev *dev, int command)
+{
+ int ret;
+
+ ret = wait_event_interruptible_timeout(dev->queue,
+ (dev->int_cond && (dev->int_type == command
+ || dev->int_type == S5P_MFC_R2H_CMD_ERR_RET)),
+ msecs_to_jiffies(MFC_INT_TIMEOUT));
+ if (ret == 0) {
+ mfc_err("Interrupt (dev->int_type:%d, command:%d) timed out\n",
+ dev->int_type, command);
+ return 1;
+ } else if (ret == -ERESTARTSYS) {
+ mfc_err("Interrupted by a signal\n");
+ return 1;
+ }
+ mfc_debug(1, "Finished waiting (dev->int_type:%d, command: %d)\n",
+ dev->int_type, command);
+ if (dev->int_type == S5P_MFC_R2H_CMD_ERR_RET)
+ return 1;
+ return 0;
+}
+
+void s5p_mfc_clean_dev_int_flags(struct s5p_mfc_dev *dev)
+{
+ dev->int_cond = 0;
+ dev->int_type = 0;
+ dev->int_err = 0;
+}
+
+int s5p_mfc_wait_for_done_ctx(struct s5p_mfc_ctx *ctx,
+ int command, int interrupt)
+{
+ int ret;
+
+ if (interrupt) {
+ ret = wait_event_interruptible_timeout(ctx->queue,
+ (ctx->int_cond && (ctx->int_type == command
+ || ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET)),
+ msecs_to_jiffies(MFC_INT_TIMEOUT));
+ } else {
+ ret = wait_event_timeout(ctx->queue,
+ (ctx->int_cond && (ctx->int_type == command
+ || ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET)),
+ msecs_to_jiffies(MFC_INT_TIMEOUT));
+ }
+ if (ret == 0) {
+ mfc_err("Interrupt (ctx->int_type:%d, command:%d) timed out\n",
+ ctx->int_type, command);
+ return 1;
+ } else if (ret == -ERESTARTSYS) {
+ mfc_err("Interrupted by a signal\n");
+ return 1;
+ }
+ mfc_debug(1, "Finished waiting (ctx->int_type:%d, command: %d)\n",
+ ctx->int_type, command);
+ if (ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET)
+ return 1;
+ return 0;
+}
+
+void s5p_mfc_clean_ctx_int_flags(struct s5p_mfc_ctx *ctx)
+{
+ ctx->int_cond = 0;
+ ctx->int_type = 0;
+ ctx->int_err = 0;
+}
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h
new file mode 100644
index 0000000000..d32860db17
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/platform/samsung/mfc5/s5p_mfc_intr.h
+ *
+ * Header file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * It contains waiting functions declarations.
+ *
+ * Kamil Debski, Copyright (C) 2011 Samsung Electronics
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_INTR_H_
+#define S5P_MFC_INTR_H_
+
+#include "s5p_mfc_common.h"
+
+int s5p_mfc_wait_for_done_ctx(struct s5p_mfc_ctx *ctx,
+ int command, int interrupt);
+int s5p_mfc_wait_for_done_dev(struct s5p_mfc_dev *dev, int command);
+void s5p_mfc_clean_ctx_int_flags(struct s5p_mfc_ctx *ctx);
+void s5p_mfc_clean_dev_int_flags(struct s5p_mfc_dev *dev);
+
+#endif /* S5P_MFC_INTR_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h
new file mode 100644
index 0000000000..1a32266b7d
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Samsung Electronics Co.Ltd
+ * Authors: Marek Szyprowski <m.szyprowski@samsung.com>
+ */
+
+#ifndef S5P_MFC_IOMMU_H_
+#define S5P_MFC_IOMMU_H_
+
+#if defined(CONFIG_EXYNOS_IOMMU)
+
+#include <linux/iommu.h>
+
+static inline bool exynos_is_iommu_available(struct device *dev)
+{
+ return dev_iommu_priv_get(dev) != NULL;
+}
+
+#else
+
+static inline bool exynos_is_iommu_available(struct device *dev)
+{
+ return false;
+}
+
+#endif
+
+#endif /* S5P_MFC_IOMMU_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c
new file mode 100644
index 0000000000..6739623011
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c
+ *
+ * Samsung MFC (Multi Function Codec - FIMV) driver
+ * This file contains hw related functions.
+ *
+ * Kamil Debski, Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_opr_v5.h"
+#include "s5p_mfc_opr_v6.h"
+
+static struct s5p_mfc_hw_ops *s5p_mfc_ops;
+
+void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev)
+{
+ if (IS_MFCV6_PLUS(dev)) {
+ s5p_mfc_ops = s5p_mfc_init_hw_ops_v6();
+ dev->warn_start = S5P_FIMV_ERR_WARNINGS_START_V6;
+ } else {
+ s5p_mfc_ops = s5p_mfc_init_hw_ops_v5();
+ dev->warn_start = S5P_FIMV_ERR_WARNINGS_START;
+ }
+ dev->mfc_ops = s5p_mfc_ops;
+}
+
+void s5p_mfc_init_regs(struct s5p_mfc_dev *dev)
+{
+ if (IS_MFCV6_PLUS(dev))
+ dev->mfc_regs = s5p_mfc_init_regs_v6_plus(dev);
+}
+
+int s5p_mfc_alloc_priv_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx,
+ struct s5p_mfc_priv_buf *b)
+{
+ unsigned int bits = dev->mem_size >> PAGE_SHIFT;
+ unsigned int count = b->size >> PAGE_SHIFT;
+ unsigned int align = (SZ_64K >> PAGE_SHIFT) - 1;
+ unsigned int start, offset;
+
+ mfc_debug(3, "Allocating priv: %zu\n", b->size);
+
+ if (dev->mem_virt) {
+ start = bitmap_find_next_zero_area(dev->mem_bitmap, bits, 0, count, align);
+ if (start > bits)
+ goto no_mem;
+
+ bitmap_set(dev->mem_bitmap, start, count);
+ offset = start << PAGE_SHIFT;
+ b->virt = dev->mem_virt + offset;
+ b->dma = dev->mem_base + offset;
+ } else {
+ struct device *mem_dev = dev->mem_dev[mem_ctx];
+ dma_addr_t base = dev->dma_base[mem_ctx];
+
+ b->ctx = mem_ctx;
+ b->virt = dma_alloc_coherent(mem_dev, b->size, &b->dma, GFP_KERNEL);
+ if (!b->virt)
+ goto no_mem;
+ if (b->dma < base) {
+ mfc_err("Invalid memory configuration - buffer (%pad) is below base memory address(%pad)\n",
+ &b->dma, &base);
+ dma_free_coherent(mem_dev, b->size, b->virt, b->dma);
+ return -ENOMEM;
+ }
+ }
+
+ mfc_debug(3, "Allocated addr %p %pad\n", b->virt, &b->dma);
+ return 0;
+no_mem:
+ mfc_err("Allocating private buffer of size %zu failed\n", b->size);
+ return -ENOMEM;
+}
+
+int s5p_mfc_alloc_generic_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx,
+ struct s5p_mfc_priv_buf *b)
+{
+ struct device *mem_dev = dev->mem_dev[mem_ctx];
+
+ mfc_debug(3, "Allocating generic buf: %zu\n", b->size);
+
+ b->ctx = mem_ctx;
+ b->virt = dma_alloc_coherent(mem_dev, b->size, &b->dma, GFP_KERNEL);
+ if (!b->virt)
+ goto no_mem;
+
+ mfc_debug(3, "Allocated addr %p %pad\n", b->virt, &b->dma);
+ return 0;
+no_mem:
+ mfc_err("Allocating generic buffer of size %zu failed\n", b->size);
+ return -ENOMEM;
+}
+
+void s5p_mfc_release_priv_buf(struct s5p_mfc_dev *dev,
+ struct s5p_mfc_priv_buf *b)
+{
+ if (dev->mem_virt) {
+ unsigned int start = (b->dma - dev->mem_base) >> PAGE_SHIFT;
+ unsigned int count = b->size >> PAGE_SHIFT;
+
+ bitmap_clear(dev->mem_bitmap, start, count);
+ } else {
+ struct device *mem_dev = dev->mem_dev[b->ctx];
+
+ dma_free_coherent(mem_dev, b->size, b->virt, b->dma);
+ }
+ b->virt = NULL;
+ b->dma = 0;
+ b->size = 0;
+}
+
+void s5p_mfc_release_generic_buf(struct s5p_mfc_dev *dev,
+ struct s5p_mfc_priv_buf *b)
+{
+ struct device *mem_dev = dev->mem_dev[b->ctx];
+ dma_free_coherent(mem_dev, b->size, b->virt, b->dma);
+ b->virt = NULL;
+ b->dma = 0;
+ b->size = 0;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h
new file mode 100644
index 0000000000..b9831275f3
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h
+ *
+ * Header file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * Contains declarations of hw related functions.
+ *
+ * Kamil Debski, Copyright (C) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_OPR_H_
+#define S5P_MFC_OPR_H_
+
+#include "s5p_mfc_common.h"
+
+struct s5p_mfc_regs {
+
+ /* codec common registers */
+ void __iomem *risc_on;
+ void __iomem *risc2host_int;
+ void __iomem *host2risc_int;
+ void __iomem *risc_base_address;
+ void __iomem *mfc_reset;
+ void __iomem *host2risc_command;
+ void __iomem *risc2host_command;
+ void __iomem *mfc_bus_reset_ctrl;
+ void __iomem *firmware_version;
+ void __iomem *instance_id;
+ void __iomem *codec_type;
+ void __iomem *context_mem_addr;
+ void __iomem *context_mem_size;
+ void __iomem *pixel_format;
+ void __iomem *metadata_enable;
+ void __iomem *mfc_version;
+ void __iomem *dbg_info_enable;
+ void __iomem *dbg_buffer_addr;
+ void __iomem *dbg_buffer_size;
+ void __iomem *hed_control;
+ void __iomem *mfc_timeout_value;
+ void __iomem *hed_shared_mem_addr;
+ void __iomem *dis_shared_mem_addr;/* only v7 */
+ void __iomem *ret_instance_id;
+ void __iomem *error_code;
+ void __iomem *dbg_buffer_output_size;
+ void __iomem *metadata_status;
+ void __iomem *metadata_addr_mb_info;
+ void __iomem *metadata_size_mb_info;
+ void __iomem *dbg_info_stage_counter;
+
+ /* decoder registers */
+ void __iomem *d_crc_ctrl;
+ void __iomem *d_dec_options;
+ void __iomem *d_display_delay;
+ void __iomem *d_set_frame_width;
+ void __iomem *d_set_frame_height;
+ void __iomem *d_sei_enable;
+ void __iomem *d_min_num_dpb;
+ void __iomem *d_min_first_plane_dpb_size;
+ void __iomem *d_min_second_plane_dpb_size;
+ void __iomem *d_min_third_plane_dpb_size;/* only v8 */
+ void __iomem *d_min_num_mv;
+ void __iomem *d_mvc_num_views;
+ void __iomem *d_min_num_dis;/* only v7 */
+ void __iomem *d_min_first_dis_size;/* only v7 */
+ void __iomem *d_min_second_dis_size;/* only v7 */
+ void __iomem *d_min_third_dis_size;/* only v7 */
+ void __iomem *d_post_filter_luma_dpb0;/* v7 and v8 */
+ void __iomem *d_post_filter_luma_dpb1;/* v7 and v8 */
+ void __iomem *d_post_filter_luma_dpb2;/* only v7 */
+ void __iomem *d_post_filter_chroma_dpb0;/* v7 and v8 */
+ void __iomem *d_post_filter_chroma_dpb1;/* v7 and v8 */
+ void __iomem *d_post_filter_chroma_dpb2;/* only v7 */
+ void __iomem *d_num_dpb;
+ void __iomem *d_num_mv;
+ void __iomem *d_init_buffer_options;
+ void __iomem *d_first_plane_dpb_stride_size;/* only v8 */
+ void __iomem *d_second_plane_dpb_stride_size;/* only v8 */
+ void __iomem *d_third_plane_dpb_stride_size;/* only v8 */
+ void __iomem *d_first_plane_dpb_size;
+ void __iomem *d_second_plane_dpb_size;
+ void __iomem *d_third_plane_dpb_size;/* only v8 */
+ void __iomem *d_mv_buffer_size;
+ void __iomem *d_first_plane_dpb;
+ void __iomem *d_second_plane_dpb;
+ void __iomem *d_third_plane_dpb;
+ void __iomem *d_mv_buffer;
+ void __iomem *d_scratch_buffer_addr;
+ void __iomem *d_scratch_buffer_size;
+ void __iomem *d_metadata_buffer_addr;
+ void __iomem *d_metadata_buffer_size;
+ void __iomem *d_nal_start_options;/* v7 and v8 */
+ void __iomem *d_cpb_buffer_addr;
+ void __iomem *d_cpb_buffer_size;
+ void __iomem *d_available_dpb_flag_upper;
+ void __iomem *d_available_dpb_flag_lower;
+ void __iomem *d_cpb_buffer_offset;
+ void __iomem *d_slice_if_enable;
+ void __iomem *d_picture_tag;
+ void __iomem *d_stream_data_size;
+ void __iomem *d_dynamic_dpb_flag_upper;/* v7 and v8 */
+ void __iomem *d_dynamic_dpb_flag_lower;/* v7 and v8 */
+ void __iomem *d_display_frame_width;
+ void __iomem *d_display_frame_height;
+ void __iomem *d_display_status;
+ void __iomem *d_display_first_plane_addr;
+ void __iomem *d_display_second_plane_addr;
+ void __iomem *d_display_third_plane_addr;/* only v8 */
+ void __iomem *d_display_frame_type;
+ void __iomem *d_display_crop_info1;
+ void __iomem *d_display_crop_info2;
+ void __iomem *d_display_picture_profile;
+ void __iomem *d_display_luma_crc;/* v7 and v8 */
+ void __iomem *d_display_chroma0_crc;/* v7 and v8 */
+ void __iomem *d_display_chroma1_crc;/* only v8 */
+ void __iomem *d_display_luma_crc_top;/* only v6 */
+ void __iomem *d_display_chroma_crc_top;/* only v6 */
+ void __iomem *d_display_luma_crc_bot;/* only v6 */
+ void __iomem *d_display_chroma_crc_bot;/* only v6 */
+ void __iomem *d_display_aspect_ratio;
+ void __iomem *d_display_extended_ar;
+ void __iomem *d_decoded_frame_width;
+ void __iomem *d_decoded_frame_height;
+ void __iomem *d_decoded_status;
+ void __iomem *d_decoded_first_plane_addr;
+ void __iomem *d_decoded_second_plane_addr;
+ void __iomem *d_decoded_third_plane_addr;/* only v8 */
+ void __iomem *d_decoded_frame_type;
+ void __iomem *d_decoded_crop_info1;
+ void __iomem *d_decoded_crop_info2;
+ void __iomem *d_decoded_picture_profile;
+ void __iomem *d_decoded_nal_size;
+ void __iomem *d_decoded_luma_crc;
+ void __iomem *d_decoded_chroma0_crc;
+ void __iomem *d_decoded_chroma1_crc;/* only v8 */
+ void __iomem *d_ret_picture_tag_top;
+ void __iomem *d_ret_picture_tag_bot;
+ void __iomem *d_ret_picture_time_top;
+ void __iomem *d_ret_picture_time_bot;
+ void __iomem *d_chroma_format;
+ void __iomem *d_vc1_info;/* v7 and v8 */
+ void __iomem *d_mpeg4_info;
+ void __iomem *d_h264_info;
+ void __iomem *d_metadata_addr_concealed_mb;
+ void __iomem *d_metadata_size_concealed_mb;
+ void __iomem *d_metadata_addr_vc1_param;
+ void __iomem *d_metadata_size_vc1_param;
+ void __iomem *d_metadata_addr_sei_nal;
+ void __iomem *d_metadata_size_sei_nal;
+ void __iomem *d_metadata_addr_vui;
+ void __iomem *d_metadata_size_vui;
+ void __iomem *d_metadata_addr_mvcvui;/* v7 and v8 */
+ void __iomem *d_metadata_size_mvcvui;/* v7 and v8 */
+ void __iomem *d_mvc_view_id;
+ void __iomem *d_frame_pack_sei_avail;
+ void __iomem *d_frame_pack_arrgment_id;
+ void __iomem *d_frame_pack_sei_info;
+ void __iomem *d_frame_pack_grid_pos;
+ void __iomem *d_display_recovery_sei_info;/* v7 and v8 */
+ void __iomem *d_decoded_recovery_sei_info;/* v7 and v8 */
+ void __iomem *d_display_first_addr;/* only v7 */
+ void __iomem *d_display_second_addr;/* only v7 */
+ void __iomem *d_display_third_addr;/* only v7 */
+ void __iomem *d_decoded_first_addr;/* only v7 */
+ void __iomem *d_decoded_second_addr;/* only v7 */
+ void __iomem *d_decoded_third_addr;/* only v7 */
+ void __iomem *d_used_dpb_flag_upper;/* v7 and v8 */
+ void __iomem *d_used_dpb_flag_lower;/* v7 and v8 */
+ void __iomem *d_min_scratch_buffer_size; /* v10 */
+ void __iomem *d_static_buffer_addr; /* v10 */
+ void __iomem *d_static_buffer_size; /* v10 */
+
+ /* encoder registers */
+ void __iomem *e_frame_width;
+ void __iomem *e_frame_height;
+ void __iomem *e_cropped_frame_width;
+ void __iomem *e_cropped_frame_height;
+ void __iomem *e_frame_crop_offset;
+ void __iomem *e_enc_options;
+ void __iomem *e_picture_profile;
+ void __iomem *e_vbv_buffer_size;
+ void __iomem *e_vbv_init_delay;
+ void __iomem *e_fixed_picture_qp;
+ void __iomem *e_rc_config;
+ void __iomem *e_rc_qp_bound;
+ void __iomem *e_rc_qp_bound_pb;/* v7 and v8 */
+ void __iomem *e_rc_mode;
+ void __iomem *e_mb_rc_config;
+ void __iomem *e_padding_ctrl;
+ void __iomem *e_air_threshold;
+ void __iomem *e_mv_hor_range;
+ void __iomem *e_mv_ver_range;
+ void __iomem *e_num_dpb;
+ void __iomem *e_luma_dpb;
+ void __iomem *e_chroma_dpb;
+ void __iomem *e_me_buffer;
+ void __iomem *e_scratch_buffer_addr;
+ void __iomem *e_scratch_buffer_size;
+ void __iomem *e_tmv_buffer0;
+ void __iomem *e_tmv_buffer1;
+ void __iomem *e_ir_buffer_addr;/* v7 and v8 */
+ void __iomem *e_source_first_plane_addr;
+ void __iomem *e_source_second_plane_addr;
+ void __iomem *e_source_third_plane_addr;/* v7 and v8 */
+ void __iomem *e_source_first_plane_stride;/* v7 and v8 */
+ void __iomem *e_source_second_plane_stride;/* v7 and v8 */
+ void __iomem *e_source_third_plane_stride;/* v7 and v8 */
+ void __iomem *e_stream_buffer_addr;
+ void __iomem *e_stream_buffer_size;
+ void __iomem *e_roi_buffer_addr;
+ void __iomem *e_param_change;
+ void __iomem *e_ir_size;
+ void __iomem *e_gop_config;
+ void __iomem *e_mslice_mode;
+ void __iomem *e_mslice_size_mb;
+ void __iomem *e_mslice_size_bits;
+ void __iomem *e_frame_insertion;
+ void __iomem *e_rc_frame_rate;
+ void __iomem *e_rc_bit_rate;
+ void __iomem *e_rc_roi_ctrl;
+ void __iomem *e_picture_tag;
+ void __iomem *e_bit_count_enable;
+ void __iomem *e_max_bit_count;
+ void __iomem *e_min_bit_count;
+ void __iomem *e_metadata_buffer_addr;
+ void __iomem *e_metadata_buffer_size;
+ void __iomem *e_encoded_source_first_plane_addr;
+ void __iomem *e_encoded_source_second_plane_addr;
+ void __iomem *e_encoded_source_third_plane_addr;/* v7 and v8 */
+ void __iomem *e_stream_size;
+ void __iomem *e_slice_type;
+ void __iomem *e_picture_count;
+ void __iomem *e_ret_picture_tag;
+ void __iomem *e_stream_buffer_write_pointer; /* only v6 */
+ void __iomem *e_recon_luma_dpb_addr;
+ void __iomem *e_recon_chroma_dpb_addr;
+ void __iomem *e_metadata_addr_enc_slice;
+ void __iomem *e_metadata_size_enc_slice;
+ void __iomem *e_mpeg4_options;
+ void __iomem *e_mpeg4_hec_period;
+ void __iomem *e_aspect_ratio;
+ void __iomem *e_extended_sar;
+ void __iomem *e_h264_options;
+ void __iomem *e_h264_options_2;/* v7 and v8 */
+ void __iomem *e_h264_lf_alpha_offset;
+ void __iomem *e_h264_lf_beta_offset;
+ void __iomem *e_h264_i_period;
+ void __iomem *e_h264_fmo_slice_grp_map_type;
+ void __iomem *e_h264_fmo_num_slice_grp_minus1;
+ void __iomem *e_h264_fmo_slice_grp_change_dir;
+ void __iomem *e_h264_fmo_slice_grp_change_rate_minus1;
+ void __iomem *e_h264_fmo_run_length_minus1_0;
+ void __iomem *e_h264_aso_slice_order_0;
+ void __iomem *e_h264_chroma_qp_offset;
+ void __iomem *e_h264_num_t_layer;
+ void __iomem *e_h264_hierarchical_qp_layer0;
+ void __iomem *e_h264_frame_packing_sei_info;
+ void __iomem *e_h264_nal_control;/* v7 and v8 */
+ void __iomem *e_mvc_frame_qp_view1;
+ void __iomem *e_mvc_rc_bit_rate_view1;
+ void __iomem *e_mvc_rc_qbound_view1;
+ void __iomem *e_mvc_rc_mode_view1;
+ void __iomem *e_mvc_inter_view_prediction_on;
+ void __iomem *e_vp8_options;/* v7 and v8 */
+ void __iomem *e_vp8_filter_options;/* v7 and v8 */
+ void __iomem *e_vp8_golden_frame_option;/* v7 and v8 */
+ void __iomem *e_vp8_num_t_layer;/* v7 and v8 */
+ void __iomem *e_vp8_hierarchical_qp_layer0;/* v7 and v8 */
+ void __iomem *e_vp8_hierarchical_qp_layer1;/* v7 and v8 */
+ void __iomem *e_vp8_hierarchical_qp_layer2;/* v7 and v8 */
+ void __iomem *e_min_scratch_buffer_size; /* v10 */
+ void __iomem *e_num_t_layer; /* v10 */
+ void __iomem *e_hier_qp_layer0; /* v10 */
+ void __iomem *e_hier_bit_rate_layer0; /* v10 */
+ void __iomem *e_hevc_options; /* v10 */
+ void __iomem *e_hevc_refresh_period; /* v10 */
+ void __iomem *e_hevc_lf_beta_offset_div2; /* v10 */
+ void __iomem *e_hevc_lf_tc_offset_div2; /* v10 */
+ void __iomem *e_hevc_nal_control; /* v10 */
+};
+
+struct s5p_mfc_hw_ops {
+ int (*alloc_dec_temp_buffers)(struct s5p_mfc_ctx *ctx);
+ void (*release_dec_desc_buffer)(struct s5p_mfc_ctx *ctx);
+ int (*alloc_codec_buffers)(struct s5p_mfc_ctx *ctx);
+ void (*release_codec_buffers)(struct s5p_mfc_ctx *ctx);
+ int (*alloc_instance_buffer)(struct s5p_mfc_ctx *ctx);
+ void (*release_instance_buffer)(struct s5p_mfc_ctx *ctx);
+ int (*alloc_dev_context_buffer)(struct s5p_mfc_dev *dev);
+ void (*release_dev_context_buffer)(struct s5p_mfc_dev *dev);
+ void (*dec_calc_dpb_size)(struct s5p_mfc_ctx *ctx);
+ void (*enc_calc_src_size)(struct s5p_mfc_ctx *ctx);
+ int (*set_enc_stream_buffer)(struct s5p_mfc_ctx *ctx,
+ unsigned long addr, unsigned int size);
+ void (*set_enc_frame_buffer)(struct s5p_mfc_ctx *ctx,
+ unsigned long y_addr, unsigned long c_addr);
+ void (*get_enc_frame_buffer)(struct s5p_mfc_ctx *ctx,
+ unsigned long *y_addr, unsigned long *c_addr);
+ void (*try_run)(struct s5p_mfc_dev *dev);
+ void (*clear_int_flags)(struct s5p_mfc_dev *dev);
+ int (*get_dspl_y_adr)(struct s5p_mfc_dev *dev);
+ int (*get_dec_y_adr)(struct s5p_mfc_dev *dev);
+ int (*get_dspl_status)(struct s5p_mfc_dev *dev);
+ int (*get_dec_status)(struct s5p_mfc_dev *dev);
+ int (*get_dec_frame_type)(struct s5p_mfc_dev *dev);
+ int (*get_disp_frame_type)(struct s5p_mfc_ctx *ctx);
+ int (*get_consumed_stream)(struct s5p_mfc_dev *dev);
+ int (*get_int_reason)(struct s5p_mfc_dev *dev);
+ int (*get_int_err)(struct s5p_mfc_dev *dev);
+ int (*err_dec)(unsigned int err);
+ int (*get_img_width)(struct s5p_mfc_dev *dev);
+ int (*get_img_height)(struct s5p_mfc_dev *dev);
+ int (*get_dpb_count)(struct s5p_mfc_dev *dev);
+ int (*get_mv_count)(struct s5p_mfc_dev *dev);
+ int (*get_inst_no)(struct s5p_mfc_dev *dev);
+ int (*get_enc_strm_size)(struct s5p_mfc_dev *dev);
+ int (*get_enc_slice_type)(struct s5p_mfc_dev *dev);
+ int (*get_enc_dpb_count)(struct s5p_mfc_dev *dev);
+ unsigned int (*get_pic_type_top)(struct s5p_mfc_ctx *ctx);
+ unsigned int (*get_pic_type_bot)(struct s5p_mfc_ctx *ctx);
+ unsigned int (*get_crop_info_h)(struct s5p_mfc_ctx *ctx);
+ unsigned int (*get_crop_info_v)(struct s5p_mfc_ctx *ctx);
+ int (*get_min_scratch_buf_size)(struct s5p_mfc_dev *dev);
+ int (*get_e_min_scratch_buf_size)(struct s5p_mfc_dev *dev);
+};
+
+void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev);
+void s5p_mfc_init_regs(struct s5p_mfc_dev *dev);
+int s5p_mfc_alloc_priv_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx,
+ struct s5p_mfc_priv_buf *b);
+void s5p_mfc_release_priv_buf(struct s5p_mfc_dev *dev,
+ struct s5p_mfc_priv_buf *b);
+int s5p_mfc_alloc_generic_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx,
+ struct s5p_mfc_priv_buf *b);
+void s5p_mfc_release_generic_buf(struct s5p_mfc_dev *dev,
+ struct s5p_mfc_priv_buf *b);
+
+
+#endif /* S5P_MFC_OPR_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c
new file mode 100644
index 0000000000..28a06dc343
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c
@@ -0,0 +1,1637 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/platform/samsung/mfc5/s5p_mfc_opr_v5.c
+ *
+ * Samsung MFC (Multi Function Codec - FIMV) driver
+ * This file contains hw related functions.
+ *
+ * Kamil Debski, Copyright (c) 2011 Samsung Electronics
+ * http://www.samsung.com/
+ */
+
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_ctrl.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_pm.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_opr_v5.h"
+#include <asm/cacheflush.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+
+#define OFFSETA(x) (((x) - dev->dma_base[BANK_L_CTX]) >> MFC_OFFSET_SHIFT)
+#define OFFSETB(x) (((x) - dev->dma_base[BANK_R_CTX]) >> MFC_OFFSET_SHIFT)
+
+/* Allocate temporary buffers for decoding */
+static int s5p_mfc_alloc_dec_temp_buffers_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv;
+ int ret;
+
+ ctx->dsc.size = buf_size->dsc;
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->dsc);
+ if (ret) {
+ mfc_err("Failed to allocate temporary buffer\n");
+ return ret;
+ }
+
+ BUG_ON(ctx->dsc.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1));
+ memset(ctx->dsc.virt, 0, ctx->dsc.size);
+ wmb();
+ return 0;
+}
+
+
+/* Release temporary buffers for decoding */
+static void s5p_mfc_release_dec_desc_buffer_v5(struct s5p_mfc_ctx *ctx)
+{
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->dsc);
+}
+
+/* Allocate codec buffers */
+static int s5p_mfc_alloc_codec_buffers_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned int enc_ref_y_size = 0;
+ unsigned int enc_ref_c_size = 0;
+ unsigned int guard_width, guard_height;
+ int ret;
+
+ if (ctx->type == MFCINST_DECODER) {
+ mfc_debug(2, "Luma size:%d Chroma size:%d MV size:%d\n",
+ ctx->luma_size, ctx->chroma_size, ctx->mv_size);
+ mfc_debug(2, "Totals bufs: %d\n", ctx->total_dpb_count);
+ } else if (ctx->type == MFCINST_ENCODER) {
+ enc_ref_y_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_y_size = ALIGN(enc_ref_y_size, S5P_FIMV_NV12MT_SALIGN);
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) {
+ enc_ref_c_size = ALIGN(ctx->img_width,
+ S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN(ctx->img_height >> 1,
+ S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_c_size = ALIGN(enc_ref_c_size,
+ S5P_FIMV_NV12MT_SALIGN);
+ } else {
+ guard_width = ALIGN(ctx->img_width + 16,
+ S5P_FIMV_NV12MT_HALIGN);
+ guard_height = ALIGN((ctx->img_height >> 1) + 4,
+ S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_c_size = ALIGN(guard_width * guard_height,
+ S5P_FIMV_NV12MT_SALIGN);
+ }
+ mfc_debug(2, "recon luma size: %d chroma size: %d\n",
+ enc_ref_y_size, enc_ref_c_size);
+ } else {
+ return -EINVAL;
+ }
+ /* Codecs have different memory requirements */
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ ctx->bank1.size =
+ ALIGN(S5P_FIMV_DEC_NB_IP_SIZE +
+ S5P_FIMV_DEC_VERT_NB_MV_SIZE,
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->bank2.size = ctx->total_dpb_count * ctx->mv_size;
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ ctx->bank1.size =
+ ALIGN(S5P_FIMV_DEC_NB_DCAC_SIZE +
+ S5P_FIMV_DEC_UPNB_MV_SIZE +
+ S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE +
+ S5P_FIMV_DEC_STX_PARSER_SIZE +
+ S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE,
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ case S5P_MFC_CODEC_VC1_DEC:
+ ctx->bank1.size =
+ ALIGN(S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE +
+ S5P_FIMV_DEC_UPNB_MV_SIZE +
+ S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE +
+ S5P_FIMV_DEC_NB_DCAC_SIZE +
+ 3 * S5P_FIMV_DEC_VC1_BITPLANE_SIZE,
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ ctx->bank1.size = 0;
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_H263_DEC:
+ ctx->bank1.size =
+ ALIGN(S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE +
+ S5P_FIMV_DEC_UPNB_MV_SIZE +
+ S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE +
+ S5P_FIMV_DEC_NB_DCAC_SIZE,
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_H264_ENC:
+ ctx->bank1.size = (enc_ref_y_size * 2) +
+ S5P_FIMV_ENC_UPMV_SIZE +
+ S5P_FIMV_ENC_COLFLG_SIZE +
+ S5P_FIMV_ENC_INTRAMD_SIZE +
+ S5P_FIMV_ENC_NBORINFO_SIZE;
+ ctx->bank2.size = (enc_ref_y_size * 2) +
+ (enc_ref_c_size * 4) +
+ S5P_FIMV_ENC_INTRAPRED_SIZE;
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ ctx->bank1.size = (enc_ref_y_size * 2) +
+ S5P_FIMV_ENC_UPMV_SIZE +
+ S5P_FIMV_ENC_COLFLG_SIZE +
+ S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ ctx->bank2.size = (enc_ref_y_size * 2) +
+ (enc_ref_c_size * 4);
+ break;
+ case S5P_MFC_CODEC_H263_ENC:
+ ctx->bank1.size = (enc_ref_y_size * 2) +
+ S5P_FIMV_ENC_UPMV_SIZE +
+ S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ ctx->bank2.size = (enc_ref_y_size * 2) +
+ (enc_ref_c_size * 4);
+ break;
+ default:
+ break;
+ }
+ /* Allocate only if memory from bank 1 is necessary */
+ if (ctx->bank1.size > 0) {
+
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->bank1);
+ if (ret) {
+ mfc_err("Failed to allocate Bank1 temporary buffer\n");
+ return ret;
+ }
+ BUG_ON(ctx->bank1.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1));
+ }
+ /* Allocate only if memory from bank 2 is necessary */
+ if (ctx->bank2.size > 0) {
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_R_CTX, &ctx->bank2);
+ if (ret) {
+ mfc_err("Failed to allocate Bank2 temporary buffer\n");
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank1);
+ return ret;
+ }
+ BUG_ON(ctx->bank2.dma & ((1 << MFC_BANK2_ALIGN_ORDER) - 1));
+ }
+ return 0;
+}
+
+/* Release buffers allocated for codec */
+static void s5p_mfc_release_codec_buffers_v5(struct s5p_mfc_ctx *ctx)
+{
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank1);
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank2);
+}
+
+/* Allocate memory for instance data buffer */
+static int s5p_mfc_alloc_instance_buffer_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv;
+ int ret;
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC ||
+ ctx->codec_mode == S5P_MFC_CODEC_H264_ENC)
+ ctx->ctx.size = buf_size->h264_ctx;
+ else
+ ctx->ctx.size = buf_size->non_h264_ctx;
+
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->ctx);
+ if (ret) {
+ mfc_err("Failed to allocate instance buffer\n");
+ return ret;
+ }
+ ctx->ctx.ofs = OFFSETA(ctx->ctx.dma);
+
+ /* Zero content of the allocated memory */
+ memset(ctx->ctx.virt, 0, ctx->ctx.size);
+ wmb();
+
+ /* Initialize shared memory */
+ ctx->shm.size = buf_size->shm;
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->shm);
+ if (ret) {
+ mfc_err("Failed to allocate shared memory buffer\n");
+ s5p_mfc_release_priv_buf(dev, &ctx->ctx);
+ return ret;
+ }
+
+ /* shared memory offset only keeps the offset from base (port a) */
+ ctx->shm.ofs = ctx->shm.dma - dev->dma_base[BANK_L_CTX];
+ BUG_ON(ctx->shm.ofs & ((1 << MFC_BANK1_ALIGN_ORDER) - 1));
+
+ memset(ctx->shm.virt, 0, buf_size->shm);
+ wmb();
+ return 0;
+}
+
+/* Release instance buffer */
+static void s5p_mfc_release_instance_buffer_v5(struct s5p_mfc_ctx *ctx)
+{
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->ctx);
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->shm);
+}
+
+static int s5p_mfc_alloc_dev_context_buffer_v5(struct s5p_mfc_dev *dev)
+{
+ /* NOP */
+
+ return 0;
+}
+
+static void s5p_mfc_release_dev_context_buffer_v5(struct s5p_mfc_dev *dev)
+{
+ /* NOP */
+}
+
+static void s5p_mfc_write_info_v5(struct s5p_mfc_ctx *ctx, unsigned int data,
+ unsigned int ofs)
+{
+ *(u32 *)(ctx->shm.virt + ofs) = data;
+ wmb();
+}
+
+static unsigned int s5p_mfc_read_info_v5(struct s5p_mfc_ctx *ctx,
+ unsigned long ofs)
+{
+ rmb();
+ return *(u32 *)(ctx->shm.virt + ofs);
+}
+
+static void s5p_mfc_dec_calc_dpb_size_v5(struct s5p_mfc_ctx *ctx)
+{
+ unsigned int guard_width, guard_height;
+
+ ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN);
+ ctx->buf_height = ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN);
+ mfc_debug(2,
+ "SEQ Done: Movie dimensions %dx%d, buffer dimensions: %dx%d\n",
+ ctx->img_width, ctx->img_height, ctx->buf_width,
+ ctx->buf_height);
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC) {
+ ctx->luma_size = ALIGN(ctx->buf_width * ctx->buf_height,
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->chroma_size = ALIGN(ctx->buf_width *
+ ALIGN((ctx->img_height >> 1),
+ S5P_FIMV_NV12MT_VALIGN),
+ S5P_FIMV_DEC_BUF_ALIGN);
+ ctx->mv_size = ALIGN(ctx->buf_width *
+ ALIGN((ctx->buf_height >> 2),
+ S5P_FIMV_NV12MT_VALIGN),
+ S5P_FIMV_DEC_BUF_ALIGN);
+ } else {
+ guard_width =
+ ALIGN(ctx->img_width + 24, S5P_FIMV_NV12MT_HALIGN);
+ guard_height =
+ ALIGN(ctx->img_height + 16, S5P_FIMV_NV12MT_VALIGN);
+ ctx->luma_size = ALIGN(guard_width * guard_height,
+ S5P_FIMV_DEC_BUF_ALIGN);
+
+ guard_width =
+ ALIGN(ctx->img_width + 16, S5P_FIMV_NV12MT_HALIGN);
+ guard_height =
+ ALIGN((ctx->img_height >> 1) + 4,
+ S5P_FIMV_NV12MT_VALIGN);
+ ctx->chroma_size = ALIGN(guard_width * guard_height,
+ S5P_FIMV_DEC_BUF_ALIGN);
+
+ ctx->mv_size = 0;
+ }
+}
+
+static void s5p_mfc_enc_calc_src_size_v5(struct s5p_mfc_ctx *ctx)
+{
+ if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) {
+ ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN);
+
+ ctx->luma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN)
+ * ALIGN(ctx->img_height, S5P_FIMV_NV12M_LVALIGN);
+ ctx->chroma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN)
+ * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12M_CVALIGN);
+
+ ctx->luma_size = ALIGN(ctx->luma_size, S5P_FIMV_NV12M_SALIGN);
+ ctx->chroma_size =
+ ALIGN(ctx->chroma_size, S5P_FIMV_NV12M_SALIGN);
+ } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT) {
+ ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN);
+
+ ctx->luma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN);
+ ctx->chroma_size =
+ ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12MT_VALIGN);
+
+ ctx->luma_size = ALIGN(ctx->luma_size, S5P_FIMV_NV12MT_SALIGN);
+ ctx->chroma_size =
+ ALIGN(ctx->chroma_size, S5P_FIMV_NV12MT_SALIGN);
+ }
+}
+
+/* Set registers for decoding temporary buffers */
+static void s5p_mfc_set_dec_desc_buffer(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv;
+
+ mfc_write(dev, OFFSETA(ctx->dsc.dma), S5P_FIMV_SI_CH0_DESC_ADR);
+ mfc_write(dev, buf_size->dsc, S5P_FIMV_SI_CH0_DESC_SIZE);
+}
+
+/* Set registers for shared buffer */
+static void s5p_mfc_set_shared_buffer(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ mfc_write(dev, ctx->shm.ofs, S5P_FIMV_SI_CH0_HOST_WR_ADR);
+}
+
+/* Set registers for decoding stream buffer */
+static int s5p_mfc_set_dec_stream_buffer_v5(struct s5p_mfc_ctx *ctx,
+ int buf_addr, unsigned int start_num_byte,
+ unsigned int buf_size)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ mfc_write(dev, OFFSETA(buf_addr), S5P_FIMV_SI_CH0_SB_ST_ADR);
+ mfc_write(dev, ctx->dec_src_buf_size, S5P_FIMV_SI_CH0_CPB_SIZE);
+ mfc_write(dev, buf_size, S5P_FIMV_SI_CH0_SB_FRM_SIZE);
+ s5p_mfc_write_info_v5(ctx, start_num_byte, START_BYTE_NUM);
+ return 0;
+}
+
+/* Set decoding frame buffer */
+static int s5p_mfc_set_dec_frame_buffer_v5(struct s5p_mfc_ctx *ctx)
+{
+ unsigned int frame_size_lu, i;
+ unsigned int frame_size_ch, frame_size_mv;
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned int dpb;
+ size_t buf_addr1, buf_addr2;
+ int buf_size1, buf_size2;
+
+ buf_addr1 = ctx->bank1.dma;
+ buf_size1 = ctx->bank1.size;
+ buf_addr2 = ctx->bank2.dma;
+ buf_size2 = ctx->bank2.size;
+ dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) &
+ ~S5P_FIMV_DPB_COUNT_MASK;
+ mfc_write(dev, ctx->total_dpb_count | dpb,
+ S5P_FIMV_SI_CH0_DPB_CONF_CTRL);
+ s5p_mfc_set_shared_buffer(ctx);
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_H264_VERT_NB_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_VERT_NB_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_VERT_NB_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H264_NB_IP_ADR);
+ buf_addr1 += S5P_FIMV_DEC_NB_IP_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_NB_IP_SIZE;
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_NB_DCAC_ADR);
+ buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_UP_NB_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_SA_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_SP_ADR);
+ buf_addr1 += S5P_FIMV_DEC_STX_PARSER_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_STX_PARSER_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_OT_LINE_ADR);
+ buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ break;
+ case S5P_MFC_CODEC_H263_DEC:
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_OT_LINE_ADR);
+ buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_UP_NB_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_SA_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_NB_DCAC_ADR);
+ buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE;
+ break;
+ case S5P_MFC_CODEC_VC1_DEC:
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_NB_DCAC_ADR);
+ buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_OT_LINE_ADR);
+ buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_UP_NB_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_SA_MV_ADR);
+ buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE3_ADR);
+ buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE2_ADR);
+ buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE1_ADR);
+ buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE;
+ break;
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ break;
+ default:
+ mfc_err("Unknown codec for decoding (%x)\n",
+ ctx->codec_mode);
+ return -EINVAL;
+ }
+ frame_size_lu = ctx->luma_size;
+ frame_size_ch = ctx->chroma_size;
+ frame_size_mv = ctx->mv_size;
+ mfc_debug(2, "Frm size: %d ch: %d mv: %d\n", frame_size_lu, frame_size_ch,
+ frame_size_mv);
+ for (i = 0; i < ctx->total_dpb_count; i++) {
+ /* Bank2 */
+ mfc_debug(2, "Luma %d: %zx\n", i,
+ ctx->dst_bufs[i].cookie.raw.luma);
+ mfc_write(dev, OFFSETB(ctx->dst_bufs[i].cookie.raw.luma),
+ S5P_FIMV_DEC_LUMA_ADR + i * 4);
+ mfc_debug(2, "\tChroma %d: %zx\n", i,
+ ctx->dst_bufs[i].cookie.raw.chroma);
+ mfc_write(dev, OFFSETA(ctx->dst_bufs[i].cookie.raw.chroma),
+ S5P_FIMV_DEC_CHROMA_ADR + i * 4);
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC) {
+ mfc_debug(2, "\tBuf2: %zx, size: %d\n",
+ buf_addr2, buf_size2);
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_H264_MV_ADR + i * 4);
+ buf_addr2 += frame_size_mv;
+ buf_size2 -= frame_size_mv;
+ }
+ }
+ mfc_debug(2, "Buf1: %zu, buf_size1: %d\n", buf_addr1, buf_size1);
+ mfc_debug(2, "Buf 1/2 size after: %d/%d (frames %d)\n",
+ buf_size1, buf_size2, ctx->total_dpb_count);
+ if (buf_size1 < 0 || buf_size2 < 0) {
+ mfc_debug(2, "Not enough memory has been allocated\n");
+ return -ENOMEM;
+ }
+ s5p_mfc_write_info_v5(ctx, frame_size_lu, ALLOC_LUMA_DPB_SIZE);
+ s5p_mfc_write_info_v5(ctx, frame_size_ch, ALLOC_CHROMA_DPB_SIZE);
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC)
+ s5p_mfc_write_info_v5(ctx, frame_size_mv, ALLOC_MV_SIZE);
+ mfc_write(dev, ((S5P_FIMV_CH_INIT_BUFS & S5P_FIMV_CH_MASK)
+ << S5P_FIMV_CH_SHIFT) | (ctx->inst_no),
+ S5P_FIMV_SI_CH0_INST_ID);
+ return 0;
+}
+
+/* Set registers for encoding stream buffer */
+static int s5p_mfc_set_enc_stream_buffer_v5(struct s5p_mfc_ctx *ctx,
+ unsigned long addr, unsigned int size)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ mfc_write(dev, OFFSETA(addr), S5P_FIMV_ENC_SI_CH0_SB_ADR);
+ mfc_write(dev, size, S5P_FIMV_ENC_SI_CH0_SB_SIZE);
+ return 0;
+}
+
+static void s5p_mfc_set_enc_frame_buffer_v5(struct s5p_mfc_ctx *ctx,
+ unsigned long y_addr, unsigned long c_addr)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ mfc_write(dev, OFFSETB(y_addr), S5P_FIMV_ENC_SI_CH0_CUR_Y_ADR);
+ mfc_write(dev, OFFSETB(c_addr), S5P_FIMV_ENC_SI_CH0_CUR_C_ADR);
+}
+
+static void s5p_mfc_get_enc_frame_buffer_v5(struct s5p_mfc_ctx *ctx,
+ unsigned long *y_addr, unsigned long *c_addr)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ *y_addr = dev->dma_base[BANK_R_CTX] +
+ (mfc_read(dev, S5P_FIMV_ENCODED_Y_ADDR) << MFC_OFFSET_SHIFT);
+ *c_addr = dev->dma_base[BANK_R_CTX] +
+ (mfc_read(dev, S5P_FIMV_ENCODED_C_ADDR) << MFC_OFFSET_SHIFT);
+}
+
+/* Set encoding ref & codec buffer */
+static int s5p_mfc_set_enc_ref_buffer_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ size_t buf_addr1, buf_addr2;
+ size_t buf_size1, buf_size2;
+ unsigned int enc_ref_y_size, enc_ref_c_size;
+ unsigned int guard_width, guard_height;
+ int i;
+
+ buf_addr1 = ctx->bank1.dma;
+ buf_size1 = ctx->bank1.size;
+ buf_addr2 = ctx->bank2.dma;
+ buf_size2 = ctx->bank2.size;
+ enc_ref_y_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_y_size = ALIGN(enc_ref_y_size, S5P_FIMV_NV12MT_SALIGN);
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) {
+ enc_ref_c_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN)
+ * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_c_size = ALIGN(enc_ref_c_size, S5P_FIMV_NV12MT_SALIGN);
+ } else {
+ guard_width = ALIGN(ctx->img_width + 16,
+ S5P_FIMV_NV12MT_HALIGN);
+ guard_height = ALIGN((ctx->img_height >> 1) + 4,
+ S5P_FIMV_NV12MT_VALIGN);
+ enc_ref_c_size = ALIGN(guard_width * guard_height,
+ S5P_FIMV_NV12MT_SALIGN);
+ }
+ mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n", buf_size1, buf_size2);
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_ENC:
+ for (i = 0; i < 2; i++) {
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i));
+ buf_addr1 += enc_ref_y_size;
+ buf_size1 -= enc_ref_y_size;
+
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_y_size;
+ buf_size2 -= enc_ref_y_size;
+ }
+ for (i = 0; i < 4; i++) {
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_c_size;
+ buf_size2 -= enc_ref_c_size;
+ }
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H264_UP_MV_ADR);
+ buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_H264_COZERO_FLAG_ADR);
+ buf_addr1 += S5P_FIMV_ENC_COLFLG_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_COLFLG_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_H264_UP_INTRA_MD_ADR);
+ buf_addr1 += S5P_FIMV_ENC_INTRAMD_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_INTRAMD_SIZE;
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_H264_UP_INTRA_PRED_ADR);
+ buf_addr2 += S5P_FIMV_ENC_INTRAPRED_SIZE;
+ buf_size2 -= S5P_FIMV_ENC_INTRAPRED_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_H264_NBOR_INFO_ADR);
+ buf_addr1 += S5P_FIMV_ENC_NBORINFO_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_NBORINFO_SIZE;
+ mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n",
+ buf_size1, buf_size2);
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ for (i = 0; i < 2; i++) {
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i));
+ buf_addr1 += enc_ref_y_size;
+ buf_size1 -= enc_ref_y_size;
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_y_size;
+ buf_size2 -= enc_ref_y_size;
+ }
+ for (i = 0; i < 4; i++) {
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_c_size;
+ buf_size2 -= enc_ref_c_size;
+ }
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_UP_MV_ADR);
+ buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_MPEG4_COZERO_FLAG_ADR);
+ buf_addr1 += S5P_FIMV_ENC_COLFLG_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_COLFLG_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_MPEG4_ACDC_COEF_ADR);
+ buf_addr1 += S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n",
+ buf_size1, buf_size2);
+ break;
+ case S5P_MFC_CODEC_H263_ENC:
+ for (i = 0; i < 2; i++) {
+ mfc_write(dev, OFFSETA(buf_addr1),
+ S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i));
+ buf_addr1 += enc_ref_y_size;
+ buf_size1 -= enc_ref_y_size;
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_y_size;
+ buf_size2 -= enc_ref_y_size;
+ }
+ for (i = 0; i < 4; i++) {
+ mfc_write(dev, OFFSETB(buf_addr2),
+ S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i));
+ buf_addr2 += enc_ref_c_size;
+ buf_size2 -= enc_ref_c_size;
+ }
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_UP_MV_ADR);
+ buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE;
+ mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_ACDC_COEF_ADR);
+ buf_addr1 += S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ buf_size1 -= S5P_FIMV_ENC_ACDCCOEF_SIZE;
+ mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n",
+ buf_size1, buf_size2);
+ break;
+ default:
+ mfc_err("Unknown codec set for encoding: %d\n",
+ ctx->codec_mode);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ unsigned int reg;
+ unsigned int shm;
+
+ /* width */
+ mfc_write(dev, ctx->img_width, S5P_FIMV_ENC_HSIZE_PX);
+ /* height */
+ mfc_write(dev, ctx->img_height, S5P_FIMV_ENC_VSIZE_PX);
+ /* pictype : enable, IDR period */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ reg |= (1 << 18);
+ reg &= ~(0xFFFF);
+ reg |= p->gop_size;
+ mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ mfc_write(dev, 0, S5P_FIMV_ENC_B_RECON_WRITE_ON);
+ /* multi-slice control */
+ /* multi-slice MB number or bit size */
+ mfc_write(dev, p->slice_mode, S5P_FIMV_ENC_MSLICE_CTRL);
+ if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) {
+ mfc_write(dev, p->slice_mb, S5P_FIMV_ENC_MSLICE_MB);
+ } else if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) {
+ mfc_write(dev, p->slice_bit, S5P_FIMV_ENC_MSLICE_BIT);
+ } else {
+ mfc_write(dev, 0, S5P_FIMV_ENC_MSLICE_MB);
+ mfc_write(dev, 0, S5P_FIMV_ENC_MSLICE_BIT);
+ }
+ /* cyclic intra refresh */
+ mfc_write(dev, p->intra_refresh_mb, S5P_FIMV_ENC_CIR_CTRL);
+ /* memory structure cur. frame */
+ if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M)
+ mfc_write(dev, 0, S5P_FIMV_ENC_MAP_FOR_CUR);
+ else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT)
+ mfc_write(dev, 3, S5P_FIMV_ENC_MAP_FOR_CUR);
+ /* padding control & value */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PADDING_CTRL);
+ if (p->pad) {
+ /** enable */
+ reg |= (1UL << 31);
+ /** cr value */
+ reg &= ~(0xFF << 16);
+ reg |= (p->pad_cr << 16);
+ /** cb value */
+ reg &= ~(0xFF << 8);
+ reg |= (p->pad_cb << 8);
+ /** y value */
+ reg &= ~(0xFF);
+ reg |= (p->pad_luma);
+ } else {
+ /** disable & all value clear */
+ reg = 0;
+ }
+ mfc_write(dev, reg, S5P_FIMV_ENC_PADDING_CTRL);
+ /* rate control config. */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG);
+ /** frame-level rate control */
+ reg &= ~(0x1 << 9);
+ reg |= (p->rc_frame << 9);
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG);
+ /* bit rate */
+ if (p->rc_frame)
+ mfc_write(dev, p->rc_bitrate,
+ S5P_FIMV_ENC_RC_BIT_RATE);
+ else
+ mfc_write(dev, 0, S5P_FIMV_ENC_RC_BIT_RATE);
+ /* reaction coefficient */
+ if (p->rc_frame)
+ mfc_write(dev, p->rc_reaction_coeff, S5P_FIMV_ENC_RC_RPARA);
+ shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL);
+ /* seq header ctrl */
+ shm &= ~(0x1 << 3);
+ shm |= (p->seq_hdr_mode << 3);
+ /* frame skip mode */
+ shm &= ~(0x3 << 1);
+ shm |= (p->frame_skip_mode << 1);
+ s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL);
+ /* fixed target bit */
+ s5p_mfc_write_info_v5(ctx, p->fixed_target_bit, RC_CONTROL_CONFIG);
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_h264_enc_params *p_264 = &p->codec.h264;
+ unsigned int reg;
+ unsigned int shm;
+
+ s5p_mfc_set_enc_params(ctx);
+ /* pictype : number of B */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ /* num_b_frame - 0 ~ 2 */
+ reg &= ~(0x3 << 16);
+ reg |= (p->num_b_frame << 16);
+ mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ /* profile & level */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PROFILE);
+ /* level */
+ reg &= ~(0xFF << 8);
+ reg |= (p_264->level << 8);
+ /* profile - 0 ~ 2 */
+ reg &= ~(0x3F);
+ reg |= p_264->profile;
+ mfc_write(dev, reg, S5P_FIMV_ENC_PROFILE);
+ /* interlace */
+ mfc_write(dev, p_264->interlace, S5P_FIMV_ENC_PIC_STRUCT);
+ /* height */
+ if (p_264->interlace)
+ mfc_write(dev, ctx->img_height >> 1, S5P_FIMV_ENC_VSIZE_PX);
+ /* loopfilter ctrl */
+ mfc_write(dev, p_264->loop_filter_mode, S5P_FIMV_ENC_LF_CTRL);
+ /* loopfilter alpha offset */
+ if (p_264->loop_filter_alpha < 0) {
+ reg = 0x10;
+ reg |= (0xFF - p_264->loop_filter_alpha) + 1;
+ } else {
+ reg = 0x00;
+ reg |= (p_264->loop_filter_alpha & 0xF);
+ }
+ mfc_write(dev, reg, S5P_FIMV_ENC_ALPHA_OFF);
+ /* loopfilter beta offset */
+ if (p_264->loop_filter_beta < 0) {
+ reg = 0x10;
+ reg |= (0xFF - p_264->loop_filter_beta) + 1;
+ } else {
+ reg = 0x00;
+ reg |= (p_264->loop_filter_beta & 0xF);
+ }
+ mfc_write(dev, reg, S5P_FIMV_ENC_BETA_OFF);
+ /* entropy coding mode */
+ if (p_264->entropy_mode == V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC)
+ mfc_write(dev, 1, S5P_FIMV_ENC_H264_ENTROPY_MODE);
+ else
+ mfc_write(dev, 0, S5P_FIMV_ENC_H264_ENTROPY_MODE);
+ /* number of ref. picture */
+ reg = mfc_read(dev, S5P_FIMV_ENC_H264_NUM_OF_REF);
+ /* num of ref. pictures of P */
+ reg &= ~(0x3 << 5);
+ reg |= (p_264->num_ref_pic_4p << 5);
+ /* max number of ref. pictures */
+ reg &= ~(0x1F);
+ reg |= p_264->max_ref_pic;
+ mfc_write(dev, reg, S5P_FIMV_ENC_H264_NUM_OF_REF);
+ /* 8x8 transform enable */
+ mfc_write(dev, p_264->_8x8_transform, S5P_FIMV_ENC_H264_TRANS_FLAG);
+ /* rate control config. */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG);
+ /* macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= (p->rc_mb << 8);
+ /* frame QP */
+ reg &= ~(0x3F);
+ reg |= p_264->rc_frame_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG);
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_denom)
+ mfc_write(dev, p->rc_framerate_num * 1000
+ / p->rc_framerate_denom, S5P_FIMV_ENC_RC_FRAME_RATE);
+ else
+ mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE);
+ /* max & min value of QP */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND);
+ /* max QP */
+ reg &= ~(0x3F << 8);
+ reg |= (p_264->rc_max_qp << 8);
+ /* min QP */
+ reg &= ~(0x3F);
+ reg |= p_264->rc_min_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND);
+ /* macroblock adaptive scaling features */
+ if (p->rc_mb) {
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_MB_CTRL);
+ /* dark region */
+ reg &= ~(0x1 << 3);
+ reg |= (p_264->rc_mb_dark << 3);
+ /* smooth region */
+ reg &= ~(0x1 << 2);
+ reg |= (p_264->rc_mb_smooth << 2);
+ /* static region */
+ reg &= ~(0x1 << 1);
+ reg |= (p_264->rc_mb_static << 1);
+ /* high activity region */
+ reg &= ~(0x1);
+ reg |= p_264->rc_mb_activity;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_MB_CTRL);
+ }
+ if (!p->rc_frame && !p->rc_mb) {
+ shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP);
+ shm &= ~(0xFFF);
+ shm |= ((p_264->rc_b_frame_qp & 0x3F) << 6);
+ shm |= (p_264->rc_p_frame_qp & 0x3F);
+ s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP);
+ }
+ /* extended encoder ctrl */
+ shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL);
+ /* AR VUI control */
+ shm &= ~(0x1 << 15);
+ shm |= (p_264->vui_sar << 1);
+ s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL);
+ if (p_264->vui_sar) {
+ /* aspect ration IDC */
+ shm = s5p_mfc_read_info_v5(ctx, SAMPLE_ASPECT_RATIO_IDC);
+ shm &= ~(0xFF);
+ shm |= p_264->vui_sar_idc;
+ s5p_mfc_write_info_v5(ctx, shm, SAMPLE_ASPECT_RATIO_IDC);
+ if (p_264->vui_sar_idc == 0xFF) {
+ /* sample AR info */
+ shm = s5p_mfc_read_info_v5(ctx, EXTENDED_SAR);
+ shm &= ~(0xFFFFFFFF);
+ shm |= p_264->vui_ext_sar_width << 16;
+ shm |= p_264->vui_ext_sar_height;
+ s5p_mfc_write_info_v5(ctx, shm, EXTENDED_SAR);
+ }
+ }
+ /* intra picture period for H.264 */
+ shm = s5p_mfc_read_info_v5(ctx, H264_I_PERIOD);
+ /* control */
+ shm &= ~(0x1 << 16);
+ shm |= (p_264->open_gop << 16);
+ /* value */
+ if (p_264->open_gop) {
+ shm &= ~(0xFFFF);
+ shm |= p_264->open_gop_size;
+ }
+ s5p_mfc_write_info_v5(ctx, shm, H264_I_PERIOD);
+ /* extended encoder ctrl */
+ shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL);
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ shm &= ~(0xFFFF << 16);
+ shm |= (p_264->cpb_size << 16);
+ }
+ s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL);
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_mpeg4(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_mpeg4_enc_params *p_mpeg4 = &p->codec.mpeg4;
+ unsigned int reg;
+ unsigned int shm;
+ unsigned int framerate;
+
+ s5p_mfc_set_enc_params(ctx);
+ /* pictype : number of B */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ /* num_b_frame - 0 ~ 2 */
+ reg &= ~(0x3 << 16);
+ reg |= (p->num_b_frame << 16);
+ mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL);
+ /* profile & level */
+ reg = mfc_read(dev, S5P_FIMV_ENC_PROFILE);
+ /* level */
+ reg &= ~(0xFF << 8);
+ reg |= (p_mpeg4->level << 8);
+ /* profile - 0 ~ 2 */
+ reg &= ~(0x3F);
+ reg |= p_mpeg4->profile;
+ mfc_write(dev, reg, S5P_FIMV_ENC_PROFILE);
+ /* quarter_pixel */
+ mfc_write(dev, p_mpeg4->quarter_pixel, S5P_FIMV_ENC_MPEG4_QUART_PXL);
+ /* qp */
+ if (!p->rc_frame) {
+ shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP);
+ shm &= ~(0xFFF);
+ shm |= ((p_mpeg4->rc_b_frame_qp & 0x3F) << 6);
+ shm |= (p_mpeg4->rc_p_frame_qp & 0x3F);
+ s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP);
+ }
+ /* frame rate */
+ if (p->rc_frame) {
+ if (p->rc_framerate_denom > 0) {
+ framerate = p->rc_framerate_num * 1000 /
+ p->rc_framerate_denom;
+ mfc_write(dev, framerate,
+ S5P_FIMV_ENC_RC_FRAME_RATE);
+ shm = s5p_mfc_read_info_v5(ctx, RC_VOP_TIMING);
+ shm &= ~(0xFFFFFFFF);
+ shm |= (1UL << 31);
+ shm |= ((p->rc_framerate_num & 0x7FFF) << 16);
+ shm |= (p->rc_framerate_denom & 0xFFFF);
+ s5p_mfc_write_info_v5(ctx, shm, RC_VOP_TIMING);
+ }
+ } else {
+ mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE);
+ }
+ /* rate control config. */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG);
+ /* frame QP */
+ reg &= ~(0x3F);
+ reg |= p_mpeg4->rc_frame_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG);
+ /* max & min value of QP */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND);
+ /* max QP */
+ reg &= ~(0x3F << 8);
+ reg |= (p_mpeg4->rc_max_qp << 8);
+ /* min QP */
+ reg &= ~(0x3F);
+ reg |= p_mpeg4->rc_min_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND);
+ /* extended encoder ctrl */
+ shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL);
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ shm &= ~(0xFFFF << 16);
+ shm |= (p->vbv_size << 16);
+ }
+ s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL);
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_h263(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_mpeg4_enc_params *p_h263 = &p->codec.mpeg4;
+ unsigned int reg;
+ unsigned int shm;
+
+ s5p_mfc_set_enc_params(ctx);
+ /* qp */
+ if (!p->rc_frame) {
+ shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP);
+ shm &= ~(0xFFF);
+ shm |= (p_h263->rc_p_frame_qp & 0x3F);
+ s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP);
+ }
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_denom)
+ mfc_write(dev, p->rc_framerate_num * 1000
+ / p->rc_framerate_denom, S5P_FIMV_ENC_RC_FRAME_RATE);
+ else
+ mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE);
+ /* rate control config. */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG);
+ /* frame QP */
+ reg &= ~(0x3F);
+ reg |= p_h263->rc_frame_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG);
+ /* max & min value of QP */
+ reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND);
+ /* max QP */
+ reg &= ~(0x3F << 8);
+ reg |= (p_h263->rc_max_qp << 8);
+ /* min QP */
+ reg &= ~(0x3F);
+ reg |= p_h263->rc_min_qp;
+ mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND);
+ /* extended encoder ctrl */
+ shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL);
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ shm &= ~(0xFFFF << 16);
+ shm |= (p->vbv_size << 16);
+ }
+ s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL);
+ return 0;
+}
+
+/* Initialize decoding */
+static int s5p_mfc_init_decode_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ s5p_mfc_set_shared_buffer(ctx);
+ /* Setup loop filter, for decoding this is only valid for MPEG4 */
+ if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_DEC)
+ mfc_write(dev, ctx->loop_filter_mpeg4, S5P_FIMV_ENC_LF_CTRL);
+ else
+ mfc_write(dev, 0, S5P_FIMV_ENC_LF_CTRL);
+ mfc_write(dev, ((ctx->slice_interface & S5P_FIMV_SLICE_INT_MASK) <<
+ S5P_FIMV_SLICE_INT_SHIFT) | (ctx->display_delay_enable <<
+ S5P_FIMV_DDELAY_ENA_SHIFT) | ((ctx->display_delay &
+ S5P_FIMV_DDELAY_VAL_MASK) << S5P_FIMV_DDELAY_VAL_SHIFT),
+ S5P_FIMV_SI_CH0_DPB_CONF_CTRL);
+ mfc_write(dev,
+ ((S5P_FIMV_CH_SEQ_HEADER & S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT)
+ | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID);
+ return 0;
+}
+
+static void s5p_mfc_set_flush(struct s5p_mfc_ctx *ctx, int flush)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned int dpb;
+
+ if (flush)
+ dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) | (
+ S5P_FIMV_DPB_FLUSH_MASK << S5P_FIMV_DPB_FLUSH_SHIFT);
+ else
+ dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) &
+ ~(S5P_FIMV_DPB_FLUSH_MASK << S5P_FIMV_DPB_FLUSH_SHIFT);
+ mfc_write(dev, dpb, S5P_FIMV_SI_CH0_DPB_CONF_CTRL);
+}
+
+/* Decode a single frame */
+static int s5p_mfc_decode_one_frame_v5(struct s5p_mfc_ctx *ctx,
+ enum s5p_mfc_decode_arg last_frame)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ mfc_write(dev, ctx->dec_dst_flag, S5P_FIMV_SI_CH0_RELEASE_BUF);
+ s5p_mfc_set_shared_buffer(ctx);
+ s5p_mfc_set_flush(ctx, ctx->dpb_flush_flag);
+ /* Issue different commands to instance basing on whether it
+ * is the last frame or not. */
+ switch (last_frame) {
+ case MFC_DEC_FRAME:
+ mfc_write(dev, ((S5P_FIMV_CH_FRAME_START & S5P_FIMV_CH_MASK) <<
+ S5P_FIMV_CH_SHIFT) | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID);
+ break;
+ case MFC_DEC_LAST_FRAME:
+ mfc_write(dev, ((S5P_FIMV_CH_LAST_FRAME & S5P_FIMV_CH_MASK) <<
+ S5P_FIMV_CH_SHIFT) | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID);
+ break;
+ case MFC_DEC_RES_CHANGE:
+ mfc_write(dev, ((S5P_FIMV_CH_FRAME_START_REALLOC &
+ S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT) | (ctx->inst_no),
+ S5P_FIMV_SI_CH0_INST_ID);
+ break;
+ }
+ mfc_debug(2, "Decoding a usual frame\n");
+ return 0;
+}
+
+static int s5p_mfc_init_encode_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC)
+ s5p_mfc_set_enc_params_h264(ctx);
+ else if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_ENC)
+ s5p_mfc_set_enc_params_mpeg4(ctx);
+ else if (ctx->codec_mode == S5P_MFC_CODEC_H263_ENC)
+ s5p_mfc_set_enc_params_h263(ctx);
+ else {
+ mfc_err("Unknown codec for encoding (%x)\n",
+ ctx->codec_mode);
+ return -EINVAL;
+ }
+ s5p_mfc_set_shared_buffer(ctx);
+ mfc_write(dev, ((S5P_FIMV_CH_SEQ_HEADER << 16) & 0x70000) |
+ (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID);
+ return 0;
+}
+
+/* Encode a single frame */
+static int s5p_mfc_encode_one_frame_v5(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ int cmd;
+ /* memory structure cur. frame */
+ if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M)
+ mfc_write(dev, 0, S5P_FIMV_ENC_MAP_FOR_CUR);
+ else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT)
+ mfc_write(dev, 3, S5P_FIMV_ENC_MAP_FOR_CUR);
+ s5p_mfc_set_shared_buffer(ctx);
+
+ if (ctx->state == MFCINST_FINISHING)
+ cmd = S5P_FIMV_CH_LAST_FRAME;
+ else
+ cmd = S5P_FIMV_CH_FRAME_START;
+ mfc_write(dev, ((cmd & S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT)
+ | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID);
+
+ return 0;
+}
+
+static void s5p_mfc_run_res_change(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ s5p_mfc_set_dec_stream_buffer_v5(ctx, 0, 0, 0);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_decode_one_frame_v5(ctx, MFC_DEC_RES_CHANGE);
+}
+
+static int s5p_mfc_run_dec_frame(struct s5p_mfc_ctx *ctx, int last_frame)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *temp_vb;
+
+ if (ctx->state == MFCINST_FINISHING) {
+ last_frame = MFC_DEC_LAST_FRAME;
+ s5p_mfc_set_dec_stream_buffer_v5(ctx, 0, 0, 0);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_decode_one_frame_v5(ctx, last_frame);
+ return 0;
+ }
+
+ /* Frames are being decoded */
+ if (list_empty(&ctx->src_queue)) {
+ mfc_debug(2, "No src buffers\n");
+ return -EAGAIN;
+ }
+ /* Get the next source buffer */
+ temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ temp_vb->flags |= MFC_BUF_FLAG_USED;
+ s5p_mfc_set_dec_stream_buffer_v5(ctx,
+ vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0),
+ ctx->consumed_stream, temp_vb->b->vb2_buf.planes[0].bytesused);
+ dev->curr_ctx = ctx->num;
+ if (temp_vb->b->vb2_buf.planes[0].bytesused == 0) {
+ last_frame = MFC_DEC_LAST_FRAME;
+ mfc_debug(2, "Setting ctx->state to FINISHING\n");
+ ctx->state = MFCINST_FINISHING;
+ }
+ s5p_mfc_decode_one_frame_v5(ctx, last_frame);
+ return 0;
+}
+
+static int s5p_mfc_run_enc_frame(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ struct s5p_mfc_buf *src_mb;
+ unsigned long src_y_addr, src_c_addr, dst_addr;
+ unsigned int dst_size;
+
+ if (list_empty(&ctx->src_queue) && ctx->state != MFCINST_FINISHING) {
+ mfc_debug(2, "no src buffers\n");
+ return -EAGAIN;
+ }
+ if (list_empty(&ctx->dst_queue)) {
+ mfc_debug(2, "no dst buffers\n");
+ return -EAGAIN;
+ }
+ if (list_empty(&ctx->src_queue)) {
+ /* send null frame */
+ s5p_mfc_set_enc_frame_buffer_v5(ctx, dev->dma_base[BANK_R_CTX],
+ dev->dma_base[BANK_R_CTX]);
+ src_mb = NULL;
+ } else {
+ src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf,
+ list);
+ src_mb->flags |= MFC_BUF_FLAG_USED;
+ if (src_mb->b->vb2_buf.planes[0].bytesused == 0) {
+ /* send null frame */
+ s5p_mfc_set_enc_frame_buffer_v5(ctx,
+ dev->dma_base[BANK_R_CTX],
+ dev->dma_base[BANK_R_CTX]);
+ ctx->state = MFCINST_FINISHING;
+ } else {
+ src_y_addr = vb2_dma_contig_plane_dma_addr(
+ &src_mb->b->vb2_buf, 0);
+ src_c_addr = vb2_dma_contig_plane_dma_addr(
+ &src_mb->b->vb2_buf, 1);
+ s5p_mfc_set_enc_frame_buffer_v5(ctx, src_y_addr,
+ src_c_addr);
+ if (src_mb->flags & MFC_BUF_FLAG_EOS)
+ ctx->state = MFCINST_FINISHING;
+ }
+ }
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_mb->flags |= MFC_BUF_FLAG_USED;
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+ s5p_mfc_set_enc_stream_buffer_v5(ctx, dst_addr, dst_size);
+ dev->curr_ctx = ctx->num;
+ mfc_debug(2, "encoding buffer with index=%d state=%d\n",
+ src_mb ? src_mb->b->vb2_buf.index : -1, ctx->state);
+ s5p_mfc_encode_one_frame_v5(ctx);
+ return 0;
+}
+
+static void s5p_mfc_run_init_dec(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *temp_vb;
+
+ /* Initializing decoding - parsing header */
+ mfc_debug(2, "Preparing to init decoding\n");
+ temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ s5p_mfc_set_dec_desc_buffer(ctx);
+ mfc_debug(2, "Header size: %d\n",
+ temp_vb->b->vb2_buf.planes[0].bytesused);
+ s5p_mfc_set_dec_stream_buffer_v5(ctx,
+ vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0),
+ 0, temp_vb->b->vb2_buf.planes[0].bytesused);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_init_decode_v5(ctx);
+}
+
+static void s5p_mfc_run_init_enc(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ unsigned long dst_addr;
+ unsigned int dst_size;
+
+ s5p_mfc_set_enc_ref_buffer_v5(ctx);
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+ s5p_mfc_set_enc_stream_buffer_v5(ctx, dst_addr, dst_size);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_init_encode_v5(ctx);
+}
+
+static int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *temp_vb;
+ int ret;
+
+ /*
+ * Header was parsed now starting processing
+ * First set the output frame buffers
+ */
+ if (ctx->capture_state != QUEUE_BUFS_MMAPED) {
+ mfc_err("It seems that not all destination buffers were mmapped\nMFC requires that all destination are mmapped before starting processing\n");
+ return -EAGAIN;
+ }
+ if (list_empty(&ctx->src_queue)) {
+ mfc_err("Header has been deallocated in the middle of initialization\n");
+ return -EIO;
+ }
+ temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ mfc_debug(2, "Header size: %d\n",
+ temp_vb->b->vb2_buf.planes[0].bytesused);
+ s5p_mfc_set_dec_stream_buffer_v5(ctx,
+ vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0),
+ 0, temp_vb->b->vb2_buf.planes[0].bytesused);
+ dev->curr_ctx = ctx->num;
+ ret = s5p_mfc_set_dec_frame_buffer_v5(ctx);
+ if (ret) {
+ mfc_err("Failed to alloc frame mem\n");
+ ctx->state = MFCINST_ERROR;
+ }
+ return ret;
+}
+
+/* Try running an operation on hardware */
+static void s5p_mfc_try_run_v5(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_ctx *ctx;
+ int new_ctx;
+ unsigned int ret = 0;
+
+ if (test_bit(0, &dev->enter_suspend)) {
+ mfc_debug(1, "Entering suspend so do not schedule any jobs\n");
+ return;
+ }
+ /* Check whether hardware is not running */
+ if (test_and_set_bit(0, &dev->hw_lock) != 0) {
+ /* This is perfectly ok, the scheduled ctx should wait */
+ mfc_debug(1, "Couldn't lock HW\n");
+ return;
+ }
+ /* Choose the context to run */
+ new_ctx = s5p_mfc_get_new_ctx(dev);
+ if (new_ctx < 0) {
+ /* No contexts to run */
+ if (test_and_clear_bit(0, &dev->hw_lock) == 0) {
+ mfc_err("Failed to unlock hardware\n");
+ return;
+ }
+ mfc_debug(1, "No ctx is scheduled to be run\n");
+ return;
+ }
+ ctx = dev->ctx[new_ctx];
+ /* Got context to run in ctx */
+ /*
+ * Last frame has already been sent to MFC.
+ * Now obtaining frames from MFC buffer
+ */
+ s5p_mfc_clock_on();
+ s5p_mfc_clean_ctx_int_flags(ctx);
+
+ if (ctx->type == MFCINST_DECODER) {
+ s5p_mfc_set_dec_desc_buffer(ctx);
+ switch (ctx->state) {
+ case MFCINST_FINISHING:
+ s5p_mfc_run_dec_frame(ctx, MFC_DEC_LAST_FRAME);
+ break;
+ case MFCINST_RUNNING:
+ ret = s5p_mfc_run_dec_frame(ctx, MFC_DEC_FRAME);
+ break;
+ case MFCINST_INIT:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_RETURN_INST:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_GOT_INST:
+ s5p_mfc_run_init_dec(ctx);
+ break;
+ case MFCINST_HEAD_PARSED:
+ ret = s5p_mfc_run_init_dec_buffers(ctx);
+ mfc_debug(1, "head parsed\n");
+ break;
+ case MFCINST_RES_CHANGE_INIT:
+ s5p_mfc_run_res_change(ctx);
+ break;
+ case MFCINST_RES_CHANGE_FLUSH:
+ s5p_mfc_run_dec_frame(ctx, MFC_DEC_FRAME);
+ break;
+ case MFCINST_RES_CHANGE_END:
+ mfc_debug(2, "Finished remaining frames after resolution change\n");
+ ctx->capture_state = QUEUE_FREE;
+ mfc_debug(2, "Will re-init the codec\n");
+ s5p_mfc_run_init_dec(ctx);
+ break;
+ default:
+ ret = -EAGAIN;
+ }
+ } else if (ctx->type == MFCINST_ENCODER) {
+ switch (ctx->state) {
+ case MFCINST_FINISHING:
+ case MFCINST_RUNNING:
+ ret = s5p_mfc_run_enc_frame(ctx);
+ break;
+ case MFCINST_INIT:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_RETURN_INST:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_GOT_INST:
+ s5p_mfc_run_init_enc(ctx);
+ break;
+ default:
+ ret = -EAGAIN;
+ }
+ } else {
+ mfc_err("Invalid context type: %d\n", ctx->type);
+ ret = -EAGAIN;
+ }
+
+ if (ret) {
+ /* Free hardware lock */
+ if (test_and_clear_bit(0, &dev->hw_lock) == 0)
+ mfc_err("Failed to unlock hardware\n");
+
+ /* This is indeed important, as no operation has been
+ * scheduled, reduce the clock count as no one will
+ * ever do this, because no interrupt related to this try_run
+ * will ever come from hardware. */
+ s5p_mfc_clock_off();
+ }
+}
+
+static void s5p_mfc_clear_int_flags_v5(struct s5p_mfc_dev *dev)
+{
+ mfc_write(dev, 0, S5P_FIMV_RISC_HOST_INT);
+ mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD);
+ mfc_write(dev, 0xffff, S5P_FIMV_SI_RTN_CHID);
+}
+
+static int s5p_mfc_get_dspl_y_adr_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_DISPLAY_Y_ADR) << MFC_OFFSET_SHIFT;
+}
+
+static int s5p_mfc_get_dec_y_adr_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_DECODE_Y_ADR) << MFC_OFFSET_SHIFT;
+}
+
+static int s5p_mfc_get_dspl_status_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_DISPLAY_STATUS);
+}
+
+static int s5p_mfc_get_dec_status_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_DECODE_STATUS);
+}
+
+static int s5p_mfc_get_dec_frame_type_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_DECODE_FRAME_TYPE) &
+ S5P_FIMV_DECODE_FRAME_MASK;
+}
+
+static int s5p_mfc_get_disp_frame_type_v5(struct s5p_mfc_ctx *ctx)
+{
+ return (s5p_mfc_read_info_v5(ctx, DISP_PIC_FRAME_TYPE) >>
+ S5P_FIMV_SHARED_DISP_FRAME_TYPE_SHIFT) &
+ S5P_FIMV_DECODE_FRAME_MASK;
+}
+
+static int s5p_mfc_get_consumed_stream_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_CONSUMED_BYTES);
+}
+
+static int s5p_mfc_get_int_reason_v5(struct s5p_mfc_dev *dev)
+{
+ int reason;
+ reason = mfc_read(dev, S5P_FIMV_RISC2HOST_CMD) &
+ S5P_FIMV_RISC2HOST_CMD_MASK;
+ switch (reason) {
+ case S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET:
+ reason = S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET:
+ reason = S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_SEQ_DONE_RET:
+ reason = S5P_MFC_R2H_CMD_SEQ_DONE_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_FRAME_DONE_RET:
+ reason = S5P_MFC_R2H_CMD_FRAME_DONE_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_SLICE_DONE_RET:
+ reason = S5P_MFC_R2H_CMD_SLICE_DONE_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_SYS_INIT_RET:
+ reason = S5P_MFC_R2H_CMD_SYS_INIT_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_FW_STATUS_RET:
+ reason = S5P_MFC_R2H_CMD_FW_STATUS_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_SLEEP_RET:
+ reason = S5P_MFC_R2H_CMD_SLEEP_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_WAKEUP_RET:
+ reason = S5P_MFC_R2H_CMD_WAKEUP_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET:
+ reason = S5P_MFC_R2H_CMD_INIT_BUFFERS_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_ENC_COMPLETE_RET:
+ reason = S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET;
+ break;
+ case S5P_FIMV_R2H_CMD_ERR_RET:
+ reason = S5P_MFC_R2H_CMD_ERR_RET;
+ break;
+ default:
+ reason = S5P_MFC_R2H_CMD_EMPTY;
+ }
+ return reason;
+}
+
+static int s5p_mfc_get_int_err_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_RISC2HOST_ARG2);
+}
+
+static int s5p_mfc_err_dec_v5(unsigned int err)
+{
+ return (err & S5P_FIMV_ERR_DEC_MASK) >> S5P_FIMV_ERR_DEC_SHIFT;
+}
+
+static int s5p_mfc_get_img_width_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_HRESOL);
+}
+
+static int s5p_mfc_get_img_height_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_VRESOL);
+}
+
+static int s5p_mfc_get_dpb_count_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_SI_BUF_NUMBER);
+}
+
+static int s5p_mfc_get_mv_count_v5(struct s5p_mfc_dev *dev)
+{
+ /* NOP */
+ return -1;
+}
+
+static int s5p_mfc_get_inst_no_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_RISC2HOST_ARG1);
+}
+
+static int s5p_mfc_get_enc_strm_size_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_ENC_SI_STRM_SIZE);
+}
+
+static int s5p_mfc_get_enc_slice_type_v5(struct s5p_mfc_dev *dev)
+{
+ return mfc_read(dev, S5P_FIMV_ENC_SI_SLICE_TYPE);
+}
+
+static int s5p_mfc_get_enc_dpb_count_v5(struct s5p_mfc_dev *dev)
+{
+ return -1;
+}
+
+static unsigned int s5p_mfc_get_pic_type_top_v5(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v5(ctx, PIC_TIME_TOP);
+}
+
+static unsigned int s5p_mfc_get_pic_type_bot_v5(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v5(ctx, PIC_TIME_BOT);
+}
+
+static unsigned int s5p_mfc_get_crop_info_h_v5(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v5(ctx, CROP_INFO_H);
+}
+
+static unsigned int s5p_mfc_get_crop_info_v_v5(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v5(ctx, CROP_INFO_V);
+}
+
+/* Initialize opr function pointers for MFC v5 */
+static struct s5p_mfc_hw_ops s5p_mfc_ops_v5 = {
+ .alloc_dec_temp_buffers = s5p_mfc_alloc_dec_temp_buffers_v5,
+ .release_dec_desc_buffer = s5p_mfc_release_dec_desc_buffer_v5,
+ .alloc_codec_buffers = s5p_mfc_alloc_codec_buffers_v5,
+ .release_codec_buffers = s5p_mfc_release_codec_buffers_v5,
+ .alloc_instance_buffer = s5p_mfc_alloc_instance_buffer_v5,
+ .release_instance_buffer = s5p_mfc_release_instance_buffer_v5,
+ .alloc_dev_context_buffer = s5p_mfc_alloc_dev_context_buffer_v5,
+ .release_dev_context_buffer = s5p_mfc_release_dev_context_buffer_v5,
+ .dec_calc_dpb_size = s5p_mfc_dec_calc_dpb_size_v5,
+ .enc_calc_src_size = s5p_mfc_enc_calc_src_size_v5,
+ .set_enc_stream_buffer = s5p_mfc_set_enc_stream_buffer_v5,
+ .set_enc_frame_buffer = s5p_mfc_set_enc_frame_buffer_v5,
+ .get_enc_frame_buffer = s5p_mfc_get_enc_frame_buffer_v5,
+ .try_run = s5p_mfc_try_run_v5,
+ .clear_int_flags = s5p_mfc_clear_int_flags_v5,
+ .get_dspl_y_adr = s5p_mfc_get_dspl_y_adr_v5,
+ .get_dec_y_adr = s5p_mfc_get_dec_y_adr_v5,
+ .get_dspl_status = s5p_mfc_get_dspl_status_v5,
+ .get_dec_status = s5p_mfc_get_dec_status_v5,
+ .get_dec_frame_type = s5p_mfc_get_dec_frame_type_v5,
+ .get_disp_frame_type = s5p_mfc_get_disp_frame_type_v5,
+ .get_consumed_stream = s5p_mfc_get_consumed_stream_v5,
+ .get_int_reason = s5p_mfc_get_int_reason_v5,
+ .get_int_err = s5p_mfc_get_int_err_v5,
+ .err_dec = s5p_mfc_err_dec_v5,
+ .get_img_width = s5p_mfc_get_img_width_v5,
+ .get_img_height = s5p_mfc_get_img_height_v5,
+ .get_dpb_count = s5p_mfc_get_dpb_count_v5,
+ .get_mv_count = s5p_mfc_get_mv_count_v5,
+ .get_inst_no = s5p_mfc_get_inst_no_v5,
+ .get_enc_strm_size = s5p_mfc_get_enc_strm_size_v5,
+ .get_enc_slice_type = s5p_mfc_get_enc_slice_type_v5,
+ .get_enc_dpb_count = s5p_mfc_get_enc_dpb_count_v5,
+ .get_pic_type_top = s5p_mfc_get_pic_type_top_v5,
+ .get_pic_type_bot = s5p_mfc_get_pic_type_bot_v5,
+ .get_crop_info_h = s5p_mfc_get_crop_info_h_v5,
+ .get_crop_info_v = s5p_mfc_get_crop_info_v_v5,
+};
+
+struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v5(void)
+{
+ return &s5p_mfc_ops_v5;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h
new file mode 100644
index 0000000000..b53d376ead
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/platform/samsung/mfc5/s5p_mfc_opr_v5.h
+ *
+ * Header file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * Contains declarations of hw related functions.
+ *
+ * Kamil Debski, Copyright (C) 2011 Samsung Electronics
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_OPR_V5_H_
+#define S5P_MFC_OPR_V5_H_
+
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_opr.h"
+
+enum MFC_SHM_OFS {
+ EXTENEDED_DECODE_STATUS = 0x00, /* D */
+ SET_FRAME_TAG = 0x04, /* D */
+ GET_FRAME_TAG_TOP = 0x08, /* D */
+ GET_FRAME_TAG_BOT = 0x0C, /* D */
+ PIC_TIME_TOP = 0x10, /* D */
+ PIC_TIME_BOT = 0x14, /* D */
+ START_BYTE_NUM = 0x18, /* D */
+
+ CROP_INFO_H = 0x20, /* D */
+ CROP_INFO_V = 0x24, /* D */
+ EXT_ENC_CONTROL = 0x28, /* E */
+ ENC_PARAM_CHANGE = 0x2C, /* E */
+ RC_VOP_TIMING = 0x30, /* E, MPEG4 */
+ HEC_PERIOD = 0x34, /* E, MPEG4 */
+ METADATA_ENABLE = 0x38, /* C */
+ METADATA_STATUS = 0x3C, /* C */
+ METADATA_DISPLAY_INDEX = 0x40, /* C */
+ EXT_METADATA_START_ADDR = 0x44, /* C */
+ PUT_EXTRADATA = 0x48, /* C */
+ EXTRADATA_ADDR = 0x4C, /* C */
+
+ ALLOC_LUMA_DPB_SIZE = 0x64, /* D */
+ ALLOC_CHROMA_DPB_SIZE = 0x68, /* D */
+ ALLOC_MV_SIZE = 0x6C, /* D */
+ P_B_FRAME_QP = 0x70, /* E */
+ SAMPLE_ASPECT_RATIO_IDC = 0x74, /* E, H.264, depend on
+ ASPECT_RATIO_VUI_ENABLE in EXT_ENC_CONTROL */
+ EXTENDED_SAR = 0x78, /* E, H.264, depned on
+ ASPECT_RATIO_VUI_ENABLE in EXT_ENC_CONTROL */
+ DISP_PIC_PROFILE = 0x7C, /* D */
+ FLUSH_CMD_TYPE = 0x80, /* C */
+ FLUSH_CMD_INBUF1 = 0x84, /* C */
+ FLUSH_CMD_INBUF2 = 0x88, /* C */
+ FLUSH_CMD_OUTBUF = 0x8C, /* E */
+ NEW_RC_BIT_RATE = 0x90, /* E, format as RC_BIT_RATE(0xC5A8)
+ depend on RC_BIT_RATE_CHANGE in ENC_PARAM_CHANGE */
+ NEW_RC_FRAME_RATE = 0x94, /* E, format as RC_FRAME_RATE(0xD0D0)
+ depend on RC_FRAME_RATE_CHANGE in ENC_PARAM_CHANGE */
+ NEW_I_PERIOD = 0x98, /* E, format as I_FRM_CTRL(0xC504)
+ depend on I_PERIOD_CHANGE in ENC_PARAM_CHANGE */
+ H264_I_PERIOD = 0x9C, /* E, H.264, open GOP */
+ RC_CONTROL_CONFIG = 0xA0, /* E */
+ BATCH_INPUT_ADDR = 0xA4, /* E */
+ BATCH_OUTPUT_ADDR = 0xA8, /* E */
+ BATCH_OUTPUT_SIZE = 0xAC, /* E */
+ MIN_LUMA_DPB_SIZE = 0xB0, /* D */
+ DEVICE_FORMAT_ID = 0xB4, /* C */
+ H264_POC_TYPE = 0xB8, /* D */
+ MIN_CHROMA_DPB_SIZE = 0xBC, /* D */
+ DISP_PIC_FRAME_TYPE = 0xC0, /* D */
+ FREE_LUMA_DPB = 0xC4, /* D, VC1 MPEG4 */
+ ASPECT_RATIO_INFO = 0xC8, /* D, MPEG4 */
+ EXTENDED_PAR = 0xCC, /* D, MPEG4 */
+ DBG_HISTORY_INPUT0 = 0xD0, /* C */
+ DBG_HISTORY_INPUT1 = 0xD4, /* C */
+ DBG_HISTORY_OUTPUT = 0xD8, /* C */
+ HIERARCHICAL_P_QP = 0xE0, /* E, H.264 */
+ FRAME_PACK_SEI_ENABLE = 0x168, /* C */
+ FRAME_PACK_SEI_AVAIL = 0x16c, /* D */
+ FRAME_PACK_SEI_INFO = 0x17c, /* E */
+};
+
+struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v5(void);
+#endif /* S5P_MFC_OPR_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c
new file mode 100644
index 0000000000..c0df5ac9fc
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c
@@ -0,0 +1,2534 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c
+ *
+ * Samsung MFC (Multi Function Codec - FIMV) driver
+ * This file contains hw related functions.
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#undef DEBUG
+
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/firmware.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/cacheflush.h>
+
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_cmd.h"
+#include "s5p_mfc_intr.h"
+#include "s5p_mfc_pm.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_opr.h"
+#include "s5p_mfc_opr_v6.h"
+
+/* #define S5P_MFC_DEBUG_REGWRITE */
+#ifdef S5P_MFC_DEBUG_REGWRITE
+#undef writel
+#define writel(v, r) \
+ do { \
+ pr_err("MFCWRITE(%p): %08x\n", r, (unsigned int)v); \
+ __raw_writel(v, r); \
+ } while (0)
+#endif /* S5P_MFC_DEBUG_REGWRITE */
+
+#define IS_MFCV6_V2(dev) (!IS_MFCV7_PLUS(dev) && dev->fw_ver == MFC_FW_V2)
+
+/* Allocate temporary buffers for decoding */
+static int s5p_mfc_alloc_dec_temp_buffers_v6(struct s5p_mfc_ctx *ctx)
+{
+ /* NOP */
+
+ return 0;
+}
+
+/* Release temporary buffers for decoding */
+static void s5p_mfc_release_dec_desc_buffer_v6(struct s5p_mfc_ctx *ctx)
+{
+ /* NOP */
+}
+
+/* Allocate codec buffers */
+static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ unsigned int mb_width, mb_height;
+ unsigned int lcu_width = 0, lcu_height = 0;
+ int ret;
+
+ mb_width = MB_WIDTH(ctx->img_width);
+ mb_height = MB_HEIGHT(ctx->img_height);
+
+ if (ctx->type == MFCINST_DECODER) {
+ mfc_debug(2, "Luma size:%d Chroma size:%d MV size:%d\n",
+ ctx->luma_size, ctx->chroma_size, ctx->mv_size);
+ mfc_debug(2, "Totals bufs: %d\n", ctx->total_dpb_count);
+ } else if (ctx->type == MFCINST_ENCODER) {
+ if (IS_MFCV10(dev)) {
+ ctx->tmv_buffer_size = 0;
+ } else if (IS_MFCV8_PLUS(dev))
+ ctx->tmv_buffer_size = S5P_FIMV_NUM_TMV_BUFFERS_V6 *
+ ALIGN(S5P_FIMV_TMV_BUFFER_SIZE_V8(mb_width, mb_height),
+ S5P_FIMV_TMV_BUFFER_ALIGN_V6);
+ else
+ ctx->tmv_buffer_size = S5P_FIMV_NUM_TMV_BUFFERS_V6 *
+ ALIGN(S5P_FIMV_TMV_BUFFER_SIZE_V6(mb_width, mb_height),
+ S5P_FIMV_TMV_BUFFER_ALIGN_V6);
+ if (IS_MFCV10(dev)) {
+ lcu_width = S5P_MFC_LCU_WIDTH(ctx->img_width);
+ lcu_height = S5P_MFC_LCU_HEIGHT(ctx->img_height);
+ if (ctx->codec_mode != S5P_FIMV_CODEC_HEVC_ENC) {
+ ctx->luma_dpb_size =
+ ALIGN((mb_width * 16), 64)
+ * ALIGN((mb_height * 16), 32)
+ + 64;
+ ctx->chroma_dpb_size =
+ ALIGN((mb_width * 16), 64)
+ * (mb_height * 8)
+ + 64;
+ } else {
+ ctx->luma_dpb_size =
+ ALIGN((lcu_width * 32), 64)
+ * ALIGN((lcu_height * 32), 32)
+ + 64;
+ ctx->chroma_dpb_size =
+ ALIGN((lcu_width * 32), 64)
+ * (lcu_height * 16)
+ + 64;
+ }
+ } else {
+ ctx->luma_dpb_size = ALIGN((mb_width * mb_height) *
+ S5P_FIMV_LUMA_MB_TO_PIXEL_V6,
+ S5P_FIMV_LUMA_DPB_BUFFER_ALIGN_V6);
+ ctx->chroma_dpb_size = ALIGN((mb_width * mb_height) *
+ S5P_FIMV_CHROMA_MB_TO_PIXEL_V6,
+ S5P_FIMV_CHROMA_DPB_BUFFER_ALIGN_V6);
+ }
+ if (IS_MFCV8_PLUS(dev))
+ ctx->me_buffer_size = ALIGN(S5P_FIMV_ME_BUFFER_SIZE_V8(
+ ctx->img_width, ctx->img_height,
+ mb_width, mb_height),
+ S5P_FIMV_ME_BUFFER_ALIGN_V6);
+ else
+ ctx->me_buffer_size = ALIGN(S5P_FIMV_ME_BUFFER_SIZE_V6(
+ ctx->img_width, ctx->img_height,
+ mb_width, mb_height),
+ S5P_FIMV_ME_BUFFER_ALIGN_V6);
+
+ mfc_debug(2, "recon luma size: %zu chroma size: %zu\n",
+ ctx->luma_dpb_size, ctx->chroma_dpb_size);
+ } else {
+ return -EINVAL;
+ }
+
+ /* Codecs have different memory requirements */
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ case S5P_MFC_CODEC_H264_MVC_DEC:
+ if (IS_MFCV10(dev))
+ mfc_debug(2, "Use min scratch buffer size\n");
+ else if (IS_MFCV8_PLUS(dev))
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V8(
+ mb_width,
+ mb_height);
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V6(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size =
+ ctx->scratch_buf_size +
+ (ctx->mv_count * ctx->mv_size);
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ if (IS_MFCV10(dev))
+ mfc_debug(2, "Use min scratch buffer size\n");
+ else if (IS_MFCV7_PLUS(dev)) {
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7(
+ mb_width,
+ mb_height);
+ } else {
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(
+ mb_width,
+ mb_height);
+ }
+
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size = ctx->scratch_buf_size;
+ break;
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ case S5P_MFC_CODEC_VC1_DEC:
+ if (IS_MFCV10(dev))
+ mfc_debug(2, "Use min scratch buffer size\n");
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_VC1_DEC_V6(
+ mb_width,
+ mb_height);
+
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size = ctx->scratch_buf_size;
+ break;
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ ctx->bank1.size = 0;
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_H263_DEC:
+ if (IS_MFCV10(dev))
+ mfc_debug(2, "Use min scratch buffer size\n");
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_H263_DEC_V6(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size = ctx->scratch_buf_size;
+ break;
+ case S5P_MFC_CODEC_VP8_DEC:
+ if (IS_MFCV10(dev))
+ mfc_debug(2, "Use min scratch buffer size\n");
+ else if (IS_MFCV8_PLUS(dev))
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V8(
+ mb_width,
+ mb_height);
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V6(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size = ctx->scratch_buf_size;
+ break;
+ case S5P_MFC_CODEC_HEVC_DEC:
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->bank1.size =
+ ctx->scratch_buf_size +
+ (ctx->mv_count * ctx->mv_size);
+ break;
+ case S5P_MFC_CODEC_VP9_DEC:
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->bank1.size =
+ ctx->scratch_buf_size +
+ DEC_VP9_STATIC_BUFFER_SIZE;
+ break;
+ case S5P_MFC_CODEC_H264_ENC:
+ if (IS_MFCV10(dev)) {
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->me_buffer_size =
+ ALIGN(ENC_V100_H264_ME_SIZE(mb_width, mb_height), 16);
+ } else if (IS_MFCV8_PLUS(dev))
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V8(
+ mb_width,
+ mb_height);
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V6(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size =
+ ctx->scratch_buf_size + ctx->tmv_buffer_size +
+ (ctx->pb_count * (ctx->luma_dpb_size +
+ ctx->chroma_dpb_size + ctx->me_buffer_size));
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ case S5P_MFC_CODEC_H263_ENC:
+ if (IS_MFCV10(dev)) {
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->me_buffer_size =
+ ALIGN(ENC_V100_MPEG4_ME_SIZE(mb_width,
+ mb_height), 16);
+ } else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_ENC_V6(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size =
+ ctx->scratch_buf_size + ctx->tmv_buffer_size +
+ (ctx->pb_count * (ctx->luma_dpb_size +
+ ctx->chroma_dpb_size + ctx->me_buffer_size));
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_VP8_ENC:
+ if (IS_MFCV10(dev)) {
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->me_buffer_size =
+ ALIGN(ENC_V100_VP8_ME_SIZE(mb_width, mb_height),
+ 16);
+ } else if (IS_MFCV8_PLUS(dev))
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V8(
+ mb_width,
+ mb_height);
+ else
+ ctx->scratch_buf_size =
+ S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7(
+ mb_width,
+ mb_height);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size,
+ S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6);
+ ctx->bank1.size =
+ ctx->scratch_buf_size + ctx->tmv_buffer_size +
+ (ctx->pb_count * (ctx->luma_dpb_size +
+ ctx->chroma_dpb_size + ctx->me_buffer_size));
+ ctx->bank2.size = 0;
+ break;
+ case S5P_MFC_CODEC_HEVC_ENC:
+ mfc_debug(2, "Use min scratch buffer size\n");
+ ctx->me_buffer_size =
+ ALIGN(ENC_V100_HEVC_ME_SIZE(lcu_width, lcu_height), 16);
+ ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, 256);
+ ctx->bank1.size =
+ ctx->scratch_buf_size + ctx->tmv_buffer_size +
+ (ctx->pb_count * (ctx->luma_dpb_size +
+ ctx->chroma_dpb_size + ctx->me_buffer_size));
+ ctx->bank2.size = 0;
+ break;
+ default:
+ break;
+ }
+
+ /* Allocate only if memory from bank 1 is necessary */
+ if (ctx->bank1.size > 0) {
+ ret = s5p_mfc_alloc_generic_buf(dev, BANK_L_CTX, &ctx->bank1);
+ if (ret) {
+ mfc_err("Failed to allocate Bank1 memory\n");
+ return ret;
+ }
+ BUG_ON(ctx->bank1.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1));
+ }
+ return 0;
+}
+
+/* Release buffers allocated for codec */
+static void s5p_mfc_release_codec_buffers_v6(struct s5p_mfc_ctx *ctx)
+{
+ s5p_mfc_release_generic_buf(ctx->dev, &ctx->bank1);
+}
+
+/* Allocate memory for instance data buffer */
+static int s5p_mfc_alloc_instance_buffer_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv;
+ int ret;
+
+ mfc_debug_enter();
+
+ switch (ctx->codec_mode) {
+ case S5P_MFC_CODEC_H264_DEC:
+ case S5P_MFC_CODEC_H264_MVC_DEC:
+ case S5P_MFC_CODEC_HEVC_DEC:
+ ctx->ctx.size = buf_size->h264_dec_ctx;
+ break;
+ case S5P_MFC_CODEC_MPEG4_DEC:
+ case S5P_MFC_CODEC_H263_DEC:
+ case S5P_MFC_CODEC_VC1RCV_DEC:
+ case S5P_MFC_CODEC_VC1_DEC:
+ case S5P_MFC_CODEC_MPEG2_DEC:
+ case S5P_MFC_CODEC_VP8_DEC:
+ case S5P_MFC_CODEC_VP9_DEC:
+ ctx->ctx.size = buf_size->other_dec_ctx;
+ break;
+ case S5P_MFC_CODEC_H264_ENC:
+ ctx->ctx.size = buf_size->h264_enc_ctx;
+ break;
+ case S5P_MFC_CODEC_HEVC_ENC:
+ ctx->ctx.size = buf_size->hevc_enc_ctx;
+ break;
+ case S5P_MFC_CODEC_MPEG4_ENC:
+ case S5P_MFC_CODEC_H263_ENC:
+ case S5P_MFC_CODEC_VP8_ENC:
+ ctx->ctx.size = buf_size->other_enc_ctx;
+ break;
+ default:
+ ctx->ctx.size = 0;
+ mfc_err("Codec type(%d) should be checked!\n", ctx->codec_mode);
+ break;
+ }
+
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->ctx);
+ if (ret) {
+ mfc_err("Failed to allocate instance buffer\n");
+ return ret;
+ }
+
+ memset(ctx->ctx.virt, 0, ctx->ctx.size);
+ wmb();
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+/* Release instance buffer */
+static void s5p_mfc_release_instance_buffer_v6(struct s5p_mfc_ctx *ctx)
+{
+ s5p_mfc_release_priv_buf(ctx->dev, &ctx->ctx);
+}
+
+/* Allocate context buffers for SYS_INIT */
+static int s5p_mfc_alloc_dev_context_buffer_v6(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv;
+ int ret;
+
+ mfc_debug_enter();
+
+ dev->ctx_buf.size = buf_size->dev_ctx;
+ ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &dev->ctx_buf);
+ if (ret) {
+ mfc_err("Failed to allocate device context buffer\n");
+ return ret;
+ }
+
+ memset(dev->ctx_buf.virt, 0, buf_size->dev_ctx);
+ wmb();
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+/* Release context buffers for SYS_INIT */
+static void s5p_mfc_release_dev_context_buffer_v6(struct s5p_mfc_dev *dev)
+{
+ s5p_mfc_release_priv_buf(dev, &dev->ctx_buf);
+}
+
+static int calc_plane(int width, int height)
+{
+ int mbX, mbY;
+
+ mbX = DIV_ROUND_UP(width, S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6);
+ mbY = DIV_ROUND_UP(height, S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6);
+
+ if (width * height < S5P_FIMV_MAX_FRAME_SIZE_V6)
+ mbY = (mbY + 1) / 2 * 2;
+
+ return (mbX * S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6) *
+ (mbY * S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6);
+}
+
+static void s5p_mfc_dec_calc_dpb_size_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN_V6);
+ ctx->buf_height = ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN_V6);
+ mfc_debug(2, "SEQ Done: Movie dimensions %dx%d,\n"
+ "buffer dimensions: %dx%d\n", ctx->img_width,
+ ctx->img_height, ctx->buf_width, ctx->buf_height);
+
+ ctx->luma_size = calc_plane(ctx->img_width, ctx->img_height);
+ ctx->chroma_size = calc_plane(ctx->img_width, (ctx->img_height >> 1));
+ if (IS_MFCV8_PLUS(ctx->dev)) {
+ /* MFCv8 needs additional 64 bytes for luma,chroma dpb*/
+ ctx->luma_size += S5P_FIMV_D_ALIGN_PLANE_SIZE_V8;
+ ctx->chroma_size += S5P_FIMV_D_ALIGN_PLANE_SIZE_V8;
+ }
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC ||
+ ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) {
+ if (IS_MFCV10(dev)) {
+ ctx->mv_size = S5P_MFC_DEC_MV_SIZE_V10(ctx->img_width,
+ ctx->img_height);
+ } else {
+ ctx->mv_size = S5P_MFC_DEC_MV_SIZE_V6(ctx->img_width,
+ ctx->img_height);
+ }
+ } else if (ctx->codec_mode == S5P_MFC_CODEC_HEVC_DEC) {
+ ctx->mv_size = s5p_mfc_dec_hevc_mv_size(ctx->img_width,
+ ctx->img_height);
+ ctx->mv_size = ALIGN(ctx->mv_size, 32);
+ } else {
+ ctx->mv_size = 0;
+ }
+}
+
+static void s5p_mfc_enc_calc_src_size_v6(struct s5p_mfc_ctx *ctx)
+{
+ unsigned int mb_width, mb_height;
+
+ mb_width = MB_WIDTH(ctx->img_width);
+ mb_height = MB_HEIGHT(ctx->img_height);
+
+ ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN_V6);
+ ctx->luma_size = ALIGN((mb_width * mb_height) * 256, 256);
+ ctx->chroma_size = ALIGN((mb_width * mb_height) * 128, 256);
+
+ /* MFCv7 needs pad bytes for Luma and Chroma */
+ if (IS_MFCV7_PLUS(ctx->dev)) {
+ ctx->luma_size += MFC_LUMA_PAD_BYTES_V7;
+ ctx->chroma_size += MFC_CHROMA_PAD_BYTES_V7;
+ }
+}
+
+/* Set registers for decoding stream buffer */
+static int s5p_mfc_set_dec_stream_buffer_v6(struct s5p_mfc_ctx *ctx,
+ int buf_addr, unsigned int start_num_byte,
+ unsigned int strm_size)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_buf_size *buf_size = dev->variant->buf_size;
+
+ mfc_debug_enter();
+ mfc_debug(2, "inst_no: %d, buf_addr: 0x%08x,\n"
+ "buf_size: 0x%08x (%d)\n",
+ ctx->inst_no, buf_addr, strm_size, strm_size);
+ writel(strm_size, mfc_regs->d_stream_data_size);
+ writel(buf_addr, mfc_regs->d_cpb_buffer_addr);
+ writel(buf_size->cpb, mfc_regs->d_cpb_buffer_size);
+ writel(start_num_byte, mfc_regs->d_cpb_buffer_offset);
+
+ mfc_debug_leave();
+ return 0;
+}
+
+/* Set decoding frame buffer */
+static int s5p_mfc_set_dec_frame_buffer_v6(struct s5p_mfc_ctx *ctx)
+{
+ unsigned int frame_size, i;
+ unsigned int frame_size_ch, frame_size_mv;
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ size_t buf_addr1;
+ int buf_size1;
+ int align_gap;
+
+ buf_addr1 = ctx->bank1.dma;
+ buf_size1 = ctx->bank1.size;
+
+ mfc_debug(2, "Buf1: %p (%d)\n", (void *)buf_addr1, buf_size1);
+ mfc_debug(2, "Total DPB COUNT: %d\n", ctx->total_dpb_count);
+ mfc_debug(2, "Setting display delay to %d\n", ctx->display_delay);
+
+ writel(ctx->total_dpb_count, mfc_regs->d_num_dpb);
+ writel(ctx->luma_size, mfc_regs->d_first_plane_dpb_size);
+ writel(ctx->chroma_size, mfc_regs->d_second_plane_dpb_size);
+
+ writel(buf_addr1, mfc_regs->d_scratch_buffer_addr);
+ writel(ctx->scratch_buf_size, mfc_regs->d_scratch_buffer_size);
+
+ if (IS_MFCV8_PLUS(dev)) {
+ writel(ctx->img_width,
+ mfc_regs->d_first_plane_dpb_stride_size);
+ writel(ctx->img_width,
+ mfc_regs->d_second_plane_dpb_stride_size);
+ }
+
+ buf_addr1 += ctx->scratch_buf_size;
+ buf_size1 -= ctx->scratch_buf_size;
+
+ if (ctx->codec_mode == S5P_FIMV_CODEC_H264_DEC ||
+ ctx->codec_mode == S5P_FIMV_CODEC_H264_MVC_DEC ||
+ ctx->codec_mode == S5P_FIMV_CODEC_HEVC_DEC) {
+ writel(ctx->mv_size, mfc_regs->d_mv_buffer_size);
+ writel(ctx->mv_count, mfc_regs->d_num_mv);
+ }
+
+ frame_size = ctx->luma_size;
+ frame_size_ch = ctx->chroma_size;
+ frame_size_mv = ctx->mv_size;
+ mfc_debug(2, "Frame size: %d ch: %d mv: %d\n",
+ frame_size, frame_size_ch, frame_size_mv);
+
+ for (i = 0; i < ctx->total_dpb_count; i++) {
+ /* Bank2 */
+ mfc_debug(2, "Luma %d: %zx\n", i,
+ ctx->dst_bufs[i].cookie.raw.luma);
+ writel(ctx->dst_bufs[i].cookie.raw.luma,
+ mfc_regs->d_first_plane_dpb + i * 4);
+ mfc_debug(2, "\tChroma %d: %zx\n", i,
+ ctx->dst_bufs[i].cookie.raw.chroma);
+ writel(ctx->dst_bufs[i].cookie.raw.chroma,
+ mfc_regs->d_second_plane_dpb + i * 4);
+ }
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC ||
+ ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC ||
+ ctx->codec_mode == S5P_MFC_CODEC_HEVC_DEC) {
+ for (i = 0; i < ctx->mv_count; i++) {
+ /* To test alignment */
+ align_gap = buf_addr1;
+ buf_addr1 = ALIGN(buf_addr1, 16);
+ align_gap = buf_addr1 - align_gap;
+ buf_size1 -= align_gap;
+
+ mfc_debug(2, "\tBuf1: %zx, size: %d\n",
+ buf_addr1, buf_size1);
+ writel(buf_addr1, mfc_regs->d_mv_buffer + i * 4);
+ buf_addr1 += frame_size_mv;
+ buf_size1 -= frame_size_mv;
+ }
+ }
+ if (ctx->codec_mode == S5P_FIMV_CODEC_VP9_DEC) {
+ writel(buf_addr1, mfc_regs->d_static_buffer_addr);
+ writel(DEC_VP9_STATIC_BUFFER_SIZE,
+ mfc_regs->d_static_buffer_size);
+ buf_addr1 += DEC_VP9_STATIC_BUFFER_SIZE;
+ buf_size1 -= DEC_VP9_STATIC_BUFFER_SIZE;
+ }
+
+ mfc_debug(2, "Buf1: %zx, buf_size1: %d (frames %d)\n",
+ buf_addr1, buf_size1, ctx->total_dpb_count);
+ if (buf_size1 < 0) {
+ mfc_debug(2, "Not enough memory has been allocated.\n");
+ return -ENOMEM;
+ }
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_INIT_BUFS_V6, NULL);
+
+ mfc_debug(2, "After setting buffers.\n");
+ return 0;
+}
+
+/* Set registers for encoding stream buffer */
+static int s5p_mfc_set_enc_stream_buffer_v6(struct s5p_mfc_ctx *ctx,
+ unsigned long addr, unsigned int size)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ writel(addr, mfc_regs->e_stream_buffer_addr); /* 16B align */
+ writel(size, mfc_regs->e_stream_buffer_size);
+
+ mfc_debug(2, "stream buf addr: 0x%08lx, size: 0x%x\n",
+ addr, size);
+
+ return 0;
+}
+
+static void s5p_mfc_set_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx,
+ unsigned long y_addr, unsigned long c_addr)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ writel(y_addr, mfc_regs->e_source_first_plane_addr);
+ writel(c_addr, mfc_regs->e_source_second_plane_addr);
+
+ mfc_debug(2, "enc src y buf addr: 0x%08lx\n", y_addr);
+ mfc_debug(2, "enc src c buf addr: 0x%08lx\n", c_addr);
+}
+
+static void s5p_mfc_get_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx,
+ unsigned long *y_addr, unsigned long *c_addr)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ unsigned long enc_recon_y_addr, enc_recon_c_addr;
+
+ *y_addr = readl(mfc_regs->e_encoded_source_first_plane_addr);
+ *c_addr = readl(mfc_regs->e_encoded_source_second_plane_addr);
+
+ enc_recon_y_addr = readl(mfc_regs->e_recon_luma_dpb_addr);
+ enc_recon_c_addr = readl(mfc_regs->e_recon_chroma_dpb_addr);
+
+ mfc_debug(2, "recon y addr: 0x%08lx y_addr: 0x%08lx\n", enc_recon_y_addr, *y_addr);
+ mfc_debug(2, "recon c addr: 0x%08lx\n", enc_recon_c_addr);
+}
+
+/* Set encoding ref & codec buffer */
+static int s5p_mfc_set_enc_ref_buffer_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ size_t buf_addr1;
+ int i, buf_size1;
+
+ mfc_debug_enter();
+
+ buf_addr1 = ctx->bank1.dma;
+ buf_size1 = ctx->bank1.size;
+
+ mfc_debug(2, "Buf1: %p (%d)\n", (void *)buf_addr1, buf_size1);
+
+ if (IS_MFCV10(dev)) {
+ /* start address of per buffer is aligned */
+ for (i = 0; i < ctx->pb_count; i++) {
+ writel(buf_addr1, mfc_regs->e_luma_dpb + (4 * i));
+ buf_addr1 += ctx->luma_dpb_size;
+ buf_size1 -= ctx->luma_dpb_size;
+ }
+ for (i = 0; i < ctx->pb_count; i++) {
+ writel(buf_addr1, mfc_regs->e_chroma_dpb + (4 * i));
+ buf_addr1 += ctx->chroma_dpb_size;
+ buf_size1 -= ctx->chroma_dpb_size;
+ }
+ for (i = 0; i < ctx->pb_count; i++) {
+ writel(buf_addr1, mfc_regs->e_me_buffer + (4 * i));
+ buf_addr1 += ctx->me_buffer_size;
+ buf_size1 -= ctx->me_buffer_size;
+ }
+ } else {
+ for (i = 0; i < ctx->pb_count; i++) {
+ writel(buf_addr1, mfc_regs->e_luma_dpb + (4 * i));
+ buf_addr1 += ctx->luma_dpb_size;
+ writel(buf_addr1, mfc_regs->e_chroma_dpb + (4 * i));
+ buf_addr1 += ctx->chroma_dpb_size;
+ writel(buf_addr1, mfc_regs->e_me_buffer + (4 * i));
+ buf_addr1 += ctx->me_buffer_size;
+ buf_size1 -= (ctx->luma_dpb_size + ctx->chroma_dpb_size
+ + ctx->me_buffer_size);
+ }
+ }
+
+ writel(buf_addr1, mfc_regs->e_scratch_buffer_addr);
+ writel(ctx->scratch_buf_size, mfc_regs->e_scratch_buffer_size);
+ buf_addr1 += ctx->scratch_buf_size;
+ buf_size1 -= ctx->scratch_buf_size;
+
+ writel(buf_addr1, mfc_regs->e_tmv_buffer0);
+ buf_addr1 += ctx->tmv_buffer_size >> 1;
+ writel(buf_addr1, mfc_regs->e_tmv_buffer1);
+ buf_addr1 += ctx->tmv_buffer_size >> 1;
+ buf_size1 -= ctx->tmv_buffer_size;
+
+ mfc_debug(2, "Buf1: %zu, buf_size1: %d (ref frames %d)\n",
+ buf_addr1, buf_size1, ctx->pb_count);
+ if (buf_size1 < 0) {
+ mfc_debug(2, "Not enough memory has been allocated.\n");
+ return -ENOMEM;
+ }
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_INIT_BUFS_V6, NULL);
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_slice_mode(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ /* multi-slice control */
+ /* multi-slice MB number or bit size */
+ writel(ctx->slice_mode, mfc_regs->e_mslice_mode);
+ if (ctx->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) {
+ writel(ctx->slice_size.mb, mfc_regs->e_mslice_size_mb);
+ } else if (ctx->slice_mode ==
+ V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) {
+ writel(ctx->slice_size.bits, mfc_regs->e_mslice_size_bits);
+ } else {
+ writel(0x0, mfc_regs->e_mslice_size_mb);
+ writel(0x0, mfc_regs->e_mslice_size_bits);
+ }
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ unsigned int reg = 0;
+
+ mfc_debug_enter();
+
+ /* width */
+ writel(ctx->img_width, mfc_regs->e_frame_width); /* 16 align */
+ /* height */
+ writel(ctx->img_height, mfc_regs->e_frame_height); /* 16 align */
+
+ /* cropped width */
+ writel(ctx->img_width, mfc_regs->e_cropped_frame_width);
+ /* cropped height */
+ writel(ctx->img_height, mfc_regs->e_cropped_frame_height);
+ /* cropped offset */
+ writel(0x0, mfc_regs->e_frame_crop_offset);
+
+ /* pictype : IDR period */
+ reg = 0;
+ reg |= p->gop_size & 0xFFFF;
+ writel(reg, mfc_regs->e_gop_config);
+
+ /* multi-slice control */
+ /* multi-slice MB number or bit size */
+ ctx->slice_mode = p->slice_mode;
+ reg = 0;
+ if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) {
+ reg |= (0x1 << 3);
+ writel(reg, mfc_regs->e_enc_options);
+ ctx->slice_size.mb = p->slice_mb;
+ } else if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) {
+ reg |= (0x1 << 3);
+ writel(reg, mfc_regs->e_enc_options);
+ ctx->slice_size.bits = p->slice_bit;
+ } else {
+ reg &= ~(0x1 << 3);
+ writel(reg, mfc_regs->e_enc_options);
+ }
+
+ s5p_mfc_set_slice_mode(ctx);
+
+ /* cyclic intra refresh */
+ writel(p->intra_refresh_mb, mfc_regs->e_ir_size);
+ reg = readl(mfc_regs->e_enc_options);
+ if (p->intra_refresh_mb == 0)
+ reg &= ~(0x1 << 4);
+ else
+ reg |= (0x1 << 4);
+ writel(reg, mfc_regs->e_enc_options);
+
+ /* 'NON_REFERENCE_STORE_ENABLE' for debugging */
+ reg = readl(mfc_regs->e_enc_options);
+ reg &= ~(0x1 << 9);
+ writel(reg, mfc_regs->e_enc_options);
+
+ /* memory structure cur. frame */
+ if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) {
+ /* 0: Linear, 1: 2D tiled*/
+ reg = readl(mfc_regs->e_enc_options);
+ reg &= ~(0x1 << 7);
+ writel(reg, mfc_regs->e_enc_options);
+ /* 0: NV12(CbCr), 1: NV21(CrCb) */
+ writel(0x0, mfc_regs->pixel_format);
+ } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV21M) {
+ /* 0: Linear, 1: 2D tiled*/
+ reg = readl(mfc_regs->e_enc_options);
+ reg &= ~(0x1 << 7);
+ writel(reg, mfc_regs->e_enc_options);
+ /* 0: NV12(CbCr), 1: NV21(CrCb) */
+ writel(0x1, mfc_regs->pixel_format);
+ } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16) {
+ /* 0: Linear, 1: 2D tiled*/
+ reg = readl(mfc_regs->e_enc_options);
+ reg |= (0x1 << 7);
+ writel(reg, mfc_regs->e_enc_options);
+ /* 0: NV12(CbCr), 1: NV21(CrCb) */
+ writel(0x0, mfc_regs->pixel_format);
+ }
+
+ /* memory structure recon. frame */
+ /* 0: Linear, 1: 2D tiled */
+ reg = readl(mfc_regs->e_enc_options);
+ reg |= (0x1 << 8);
+ writel(reg, mfc_regs->e_enc_options);
+
+ /* padding control & value */
+ writel(0x0, mfc_regs->e_padding_ctrl);
+ if (p->pad) {
+ reg = 0;
+ /** enable */
+ reg |= (1UL << 31);
+ /** cr value */
+ reg |= ((p->pad_cr & 0xFF) << 16);
+ /** cb value */
+ reg |= ((p->pad_cb & 0xFF) << 8);
+ /** y value */
+ reg |= p->pad_luma & 0xFF;
+ writel(reg, mfc_regs->e_padding_ctrl);
+ }
+
+ /* rate control config. */
+ reg = 0;
+ /* frame-level rate control */
+ reg |= ((p->rc_frame & 0x1) << 9);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* bit rate */
+ if (p->rc_frame)
+ writel(p->rc_bitrate,
+ mfc_regs->e_rc_bit_rate);
+ else
+ writel(1, mfc_regs->e_rc_bit_rate);
+
+ /* reaction coefficient */
+ if (p->rc_frame) {
+ if (p->rc_reaction_coeff < TIGHT_CBR_MAX) /* tight CBR */
+ writel(1, mfc_regs->e_rc_mode);
+ else /* loose CBR */
+ writel(2, mfc_regs->e_rc_mode);
+ }
+
+ /* seq header ctrl */
+ reg = readl(mfc_regs->e_enc_options);
+ reg &= ~(0x1 << 2);
+ reg |= ((p->seq_hdr_mode & 0x1) << 2);
+
+ /* frame skip mode */
+ reg &= ~(0x3);
+ reg |= (p->frame_skip_mode & 0x3);
+ writel(reg, mfc_regs->e_enc_options);
+
+ /* 'DROP_CONTROL_ENABLE', disable */
+ reg = readl(mfc_regs->e_rc_config);
+ reg &= ~(0x1 << 10);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* setting for MV range [16, 256] */
+ reg = (p->mv_h_range & S5P_FIMV_E_MV_RANGE_V6_MASK);
+ writel(reg, mfc_regs->e_mv_hor_range);
+
+ reg = (p->mv_v_range & S5P_FIMV_E_MV_RANGE_V6_MASK);
+ writel(reg, mfc_regs->e_mv_ver_range);
+
+ writel(0x0, mfc_regs->e_frame_insertion);
+ writel(0x0, mfc_regs->e_roi_buffer_addr);
+ writel(0x0, mfc_regs->e_param_change);
+ writel(0x0, mfc_regs->e_rc_roi_ctrl);
+ writel(0x0, mfc_regs->e_picture_tag);
+
+ writel(0x0, mfc_regs->e_bit_count_enable);
+ writel(0x0, mfc_regs->e_max_bit_count);
+ writel(0x0, mfc_regs->e_min_bit_count);
+
+ writel(0x0, mfc_regs->e_metadata_buffer_addr);
+ writel(0x0, mfc_regs->e_metadata_buffer_size);
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_h264_enc_params *p_h264 = &p->codec.h264;
+ unsigned int reg = 0;
+ int i;
+
+ mfc_debug_enter();
+
+ s5p_mfc_set_enc_params(ctx);
+
+ /* pictype : number of B */
+ reg = readl(mfc_regs->e_gop_config);
+ reg &= ~(0x3 << 16);
+ reg |= ((p->num_b_frame & 0x3) << 16);
+ writel(reg, mfc_regs->e_gop_config);
+
+ /* profile & level */
+ reg = 0;
+ /** level */
+ reg |= ((p_h264->level & 0xFF) << 8);
+ /** profile - 0 ~ 3 */
+ reg |= p_h264->profile & 0x3F;
+ writel(reg, mfc_regs->e_picture_profile);
+
+ /* rate control config. */
+ reg = readl(mfc_regs->e_rc_config);
+ /** macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= ((p->rc_mb & 0x1) << 8);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /** frame QP */
+ reg &= ~(0x3F);
+ reg |= p_h264->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* max & min value of QP */
+ reg = 0;
+ /** max QP */
+ reg |= ((p_h264->rc_max_qp & 0x3F) << 8);
+ /** min QP */
+ reg |= p_h264->rc_min_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_qp_bound);
+
+ /* other QPs */
+ writel(0x0, mfc_regs->e_fixed_picture_qp);
+ if (!p->rc_frame && !p->rc_mb) {
+ reg = 0;
+ reg |= ((p_h264->rc_b_frame_qp & 0x3F) << 16);
+ reg |= ((p_h264->rc_p_frame_qp & 0x3F) << 8);
+ reg |= p_h264->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_fixed_picture_qp);
+ }
+
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) {
+ reg = 0;
+ reg |= ((p->rc_framerate_num & 0xFFFF) << 16);
+ reg |= p->rc_framerate_denom & 0xFFFF;
+ writel(reg, mfc_regs->e_rc_frame_rate);
+ }
+
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ writel(p_h264->cpb_size & 0xFFFF,
+ mfc_regs->e_vbv_buffer_size);
+
+ if (p->rc_frame)
+ writel(p->vbv_delay, mfc_regs->e_vbv_init_delay);
+ }
+
+ /* interlace */
+ reg = 0;
+ reg |= ((p_h264->interlace & 0x1) << 3);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* height */
+ if (p_h264->interlace) {
+ writel(ctx->img_height >> 1,
+ mfc_regs->e_frame_height); /* 32 align */
+ /* cropped height */
+ writel(ctx->img_height >> 1,
+ mfc_regs->e_cropped_frame_height);
+ }
+
+ /* loop filter ctrl */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x3 << 1);
+ reg |= ((p_h264->loop_filter_mode & 0x3) << 1);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* loopfilter alpha offset */
+ if (p_h264->loop_filter_alpha < 0) {
+ reg = 0x10;
+ reg |= (0xFF - p_h264->loop_filter_alpha) + 1;
+ } else {
+ reg = 0x00;
+ reg |= (p_h264->loop_filter_alpha & 0xF);
+ }
+ writel(reg, mfc_regs->e_h264_lf_alpha_offset);
+
+ /* loopfilter beta offset */
+ if (p_h264->loop_filter_beta < 0) {
+ reg = 0x10;
+ reg |= (0xFF - p_h264->loop_filter_beta) + 1;
+ } else {
+ reg = 0x00;
+ reg |= (p_h264->loop_filter_beta & 0xF);
+ }
+ writel(reg, mfc_regs->e_h264_lf_beta_offset);
+
+ /* entropy coding mode */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1);
+ reg |= p_h264->entropy_mode & 0x1;
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* number of ref. picture */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 7);
+ reg |= (((p_h264->num_ref_pic_4p - 1) & 0x1) << 7);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* 8x8 transform enable */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x3 << 12);
+ reg |= ((p_h264->_8x8_transform & 0x3) << 12);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* macroblock adaptive scaling features */
+ writel(0x0, mfc_regs->e_mb_rc_config);
+ if (p->rc_mb) {
+ reg = 0;
+ /** dark region */
+ reg |= ((p_h264->rc_mb_dark & 0x1) << 3);
+ /** smooth region */
+ reg |= ((p_h264->rc_mb_smooth & 0x1) << 2);
+ /** static region */
+ reg |= ((p_h264->rc_mb_static & 0x1) << 1);
+ /** high activity region */
+ reg |= p_h264->rc_mb_activity & 0x1;
+ writel(reg, mfc_regs->e_mb_rc_config);
+ }
+
+ /* aspect ratio VUI */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 5);
+ reg |= ((p_h264->vui_sar & 0x1) << 5);
+ writel(reg, mfc_regs->e_h264_options);
+
+ writel(0x0, mfc_regs->e_aspect_ratio);
+ writel(0x0, mfc_regs->e_extended_sar);
+ if (p_h264->vui_sar) {
+ /* aspect ration IDC */
+ reg = 0;
+ reg |= p_h264->vui_sar_idc & 0xFF;
+ writel(reg, mfc_regs->e_aspect_ratio);
+ if (p_h264->vui_sar_idc == 0xFF) {
+ /* extended SAR */
+ reg = 0;
+ reg |= (p_h264->vui_ext_sar_width & 0xFFFF) << 16;
+ reg |= p_h264->vui_ext_sar_height & 0xFFFF;
+ writel(reg, mfc_regs->e_extended_sar);
+ }
+ }
+
+ /* intra picture period for H.264 open GOP */
+ /* control */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 4);
+ reg |= ((p_h264->open_gop & 0x1) << 4);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* value */
+ writel(0x0, mfc_regs->e_h264_i_period);
+ if (p_h264->open_gop) {
+ reg = 0;
+ reg |= p_h264->open_gop_size & 0xFFFF;
+ writel(reg, mfc_regs->e_h264_i_period);
+ }
+
+ /* 'WEIGHTED_BI_PREDICTION' for B is disable */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x3 << 9);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* 'CONSTRAINED_INTRA_PRED_ENABLE' is disable */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 14);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* ASO */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 6);
+ reg |= ((p_h264->aso & 0x1) << 6);
+ writel(reg, mfc_regs->e_h264_options);
+
+ /* hier qp enable */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 8);
+ reg |= ((p_h264->open_gop & 0x1) << 8);
+ writel(reg, mfc_regs->e_h264_options);
+ reg = 0;
+ if (p_h264->hier_qp && p_h264->hier_qp_layer) {
+ reg |= (p_h264->hier_qp_type & 0x1) << 0x3;
+ reg |= p_h264->hier_qp_layer & 0x7;
+ writel(reg, mfc_regs->e_h264_num_t_layer);
+ /* QP value for each layer */
+ for (i = 0; i < p_h264->hier_qp_layer &&
+ i < ARRAY_SIZE(p_h264->hier_qp_layer_qp); i++) {
+ writel(p_h264->hier_qp_layer_qp[i],
+ mfc_regs->e_h264_hierarchical_qp_layer0
+ + i * 4);
+ }
+ }
+ /* number of coding layer should be zero when hierarchical is disable */
+ writel(reg, mfc_regs->e_h264_num_t_layer);
+
+ /* frame packing SEI generation */
+ reg = readl(mfc_regs->e_h264_options);
+ reg &= ~(0x1 << 25);
+ reg |= ((p_h264->sei_frame_packing & 0x1) << 25);
+ writel(reg, mfc_regs->e_h264_options);
+ if (p_h264->sei_frame_packing) {
+ reg = 0;
+ /** current frame0 flag */
+ reg |= ((p_h264->sei_fp_curr_frame_0 & 0x1) << 2);
+ /** arrangement type */
+ reg |= p_h264->sei_fp_arrangement_type & 0x3;
+ writel(reg, mfc_regs->e_h264_frame_packing_sei_info);
+ }
+
+ if (p_h264->fmo) {
+ switch (p_h264->fmo_map_type) {
+ case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_INTERLEAVED_SLICES:
+ if (p_h264->fmo_slice_grp > 4)
+ p_h264->fmo_slice_grp = 4;
+ for (i = 0; i < (p_h264->fmo_slice_grp & 0xF); i++)
+ writel(p_h264->fmo_run_len[i] - 1,
+ mfc_regs->e_h264_fmo_run_length_minus1_0
+ + i * 4);
+ break;
+ case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_SCATTERED_SLICES:
+ if (p_h264->fmo_slice_grp > 4)
+ p_h264->fmo_slice_grp = 4;
+ break;
+ case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_RASTER_SCAN:
+ case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_WIPE_SCAN:
+ if (p_h264->fmo_slice_grp > 2)
+ p_h264->fmo_slice_grp = 2;
+ writel(p_h264->fmo_chg_dir & 0x1,
+ mfc_regs->e_h264_fmo_slice_grp_change_dir);
+ /* the valid range is 0 ~ number of macroblocks -1 */
+ writel(p_h264->fmo_chg_rate,
+ mfc_regs->e_h264_fmo_slice_grp_change_rate_minus1);
+ break;
+ default:
+ mfc_err("Unsupported map type for FMO: %d\n",
+ p_h264->fmo_map_type);
+ p_h264->fmo_map_type = 0;
+ p_h264->fmo_slice_grp = 1;
+ break;
+ }
+
+ writel(p_h264->fmo_map_type,
+ mfc_regs->e_h264_fmo_slice_grp_map_type);
+ writel(p_h264->fmo_slice_grp - 1,
+ mfc_regs->e_h264_fmo_num_slice_grp_minus1);
+ } else {
+ writel(0, mfc_regs->e_h264_fmo_num_slice_grp_minus1);
+ }
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_mpeg4(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_mpeg4_enc_params *p_mpeg4 = &p->codec.mpeg4;
+ unsigned int reg = 0;
+
+ mfc_debug_enter();
+
+ s5p_mfc_set_enc_params(ctx);
+
+ /* pictype : number of B */
+ reg = readl(mfc_regs->e_gop_config);
+ reg &= ~(0x3 << 16);
+ reg |= ((p->num_b_frame & 0x3) << 16);
+ writel(reg, mfc_regs->e_gop_config);
+
+ /* profile & level */
+ reg = 0;
+ /** level */
+ reg |= ((p_mpeg4->level & 0xFF) << 8);
+ /** profile - 0 ~ 1 */
+ reg |= p_mpeg4->profile & 0x3F;
+ writel(reg, mfc_regs->e_picture_profile);
+
+ /* rate control config. */
+ reg = readl(mfc_regs->e_rc_config);
+ /** macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= ((p->rc_mb & 0x1) << 8);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /** frame QP */
+ reg &= ~(0x3F);
+ reg |= p_mpeg4->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* max & min value of QP */
+ reg = 0;
+ /** max QP */
+ reg |= ((p_mpeg4->rc_max_qp & 0x3F) << 8);
+ /** min QP */
+ reg |= p_mpeg4->rc_min_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_qp_bound);
+
+ /* other QPs */
+ writel(0x0, mfc_regs->e_fixed_picture_qp);
+ if (!p->rc_frame && !p->rc_mb) {
+ reg = 0;
+ reg |= ((p_mpeg4->rc_b_frame_qp & 0x3F) << 16);
+ reg |= ((p_mpeg4->rc_p_frame_qp & 0x3F) << 8);
+ reg |= p_mpeg4->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_fixed_picture_qp);
+ }
+
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) {
+ reg = 0;
+ reg |= ((p->rc_framerate_num & 0xFFFF) << 16);
+ reg |= p->rc_framerate_denom & 0xFFFF;
+ writel(reg, mfc_regs->e_rc_frame_rate);
+ }
+
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size);
+
+ if (p->rc_frame)
+ writel(p->vbv_delay, mfc_regs->e_vbv_init_delay);
+ }
+
+ /* Disable HEC */
+ writel(0x0, mfc_regs->e_mpeg4_options);
+ writel(0x0, mfc_regs->e_mpeg4_hec_period);
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_h263(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_mpeg4_enc_params *p_h263 = &p->codec.mpeg4;
+ unsigned int reg = 0;
+
+ mfc_debug_enter();
+
+ s5p_mfc_set_enc_params(ctx);
+
+ /* profile & level */
+ reg = 0;
+ /** profile */
+ reg |= (0x1 << 4);
+ writel(reg, mfc_regs->e_picture_profile);
+
+ /* rate control config. */
+ reg = readl(mfc_regs->e_rc_config);
+ /** macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= ((p->rc_mb & 0x1) << 8);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /** frame QP */
+ reg &= ~(0x3F);
+ reg |= p_h263->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* max & min value of QP */
+ reg = 0;
+ /** max QP */
+ reg |= ((p_h263->rc_max_qp & 0x3F) << 8);
+ /** min QP */
+ reg |= p_h263->rc_min_qp & 0x3F;
+ writel(reg, mfc_regs->e_rc_qp_bound);
+
+ /* other QPs */
+ writel(0x0, mfc_regs->e_fixed_picture_qp);
+ if (!p->rc_frame && !p->rc_mb) {
+ reg = 0;
+ reg |= ((p_h263->rc_b_frame_qp & 0x3F) << 16);
+ reg |= ((p_h263->rc_p_frame_qp & 0x3F) << 8);
+ reg |= p_h263->rc_frame_qp & 0x3F;
+ writel(reg, mfc_regs->e_fixed_picture_qp);
+ }
+
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) {
+ reg = 0;
+ reg |= ((p->rc_framerate_num & 0xFFFF) << 16);
+ reg |= p->rc_framerate_denom & 0xFFFF;
+ writel(reg, mfc_regs->e_rc_frame_rate);
+ }
+
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size);
+
+ if (p->rc_frame)
+ writel(p->vbv_delay, mfc_regs->e_vbv_init_delay);
+ }
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_vp8(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_vp8_enc_params *p_vp8 = &p->codec.vp8;
+ unsigned int reg = 0;
+ unsigned int val = 0;
+
+ mfc_debug_enter();
+
+ s5p_mfc_set_enc_params(ctx);
+
+ /* pictype : number of B */
+ reg = readl(mfc_regs->e_gop_config);
+ reg &= ~(0x3 << 16);
+ reg |= ((p->num_b_frame & 0x3) << 16);
+ writel(reg, mfc_regs->e_gop_config);
+
+ /* profile - 0 ~ 3 */
+ reg = p_vp8->profile & 0x3;
+ writel(reg, mfc_regs->e_picture_profile);
+
+ /* rate control config. */
+ reg = readl(mfc_regs->e_rc_config);
+ /** macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= ((p->rc_mb & 0x1) << 8);
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* frame rate */
+ if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) {
+ reg = 0;
+ reg |= ((p->rc_framerate_num & 0xFFFF) << 16);
+ reg |= p->rc_framerate_denom & 0xFFFF;
+ writel(reg, mfc_regs->e_rc_frame_rate);
+ }
+
+ /* frame QP */
+ reg &= ~(0x7F);
+ reg |= p_vp8->rc_frame_qp & 0x7F;
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* other QPs */
+ writel(0x0, mfc_regs->e_fixed_picture_qp);
+ if (!p->rc_frame && !p->rc_mb) {
+ reg = 0;
+ reg |= ((p_vp8->rc_p_frame_qp & 0x7F) << 8);
+ reg |= p_vp8->rc_frame_qp & 0x7F;
+ writel(reg, mfc_regs->e_fixed_picture_qp);
+ }
+
+ /* max QP */
+ reg = ((p_vp8->rc_max_qp & 0x7F) << 8);
+ /* min QP */
+ reg |= p_vp8->rc_min_qp & 0x7F;
+ writel(reg, mfc_regs->e_rc_qp_bound);
+
+ /* vbv buffer size */
+ if (p->frame_skip_mode ==
+ V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) {
+ writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size);
+
+ if (p->rc_frame)
+ writel(p->vbv_delay, mfc_regs->e_vbv_init_delay);
+ }
+
+ /* VP8 specific params */
+ reg = 0;
+ reg |= (p_vp8->imd_4x4 & 0x1) << 10;
+ switch (p_vp8->num_partitions) {
+ case V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION:
+ val = 0;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_2_PARTITIONS:
+ val = 2;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_4_PARTITIONS:
+ val = 4;
+ break;
+ case V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS:
+ val = 8;
+ break;
+ }
+ reg |= (val & 0xF) << 3;
+ reg |= (p_vp8->num_ref & 0x2);
+ writel(reg, mfc_regs->e_vp8_options);
+
+ mfc_debug_leave();
+
+ return 0;
+}
+
+static int s5p_mfc_set_enc_params_hevc(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_hevc_enc_params *p_hevc = &p->codec.hevc;
+ unsigned int reg = 0;
+ int i;
+
+ mfc_debug_enter();
+
+ s5p_mfc_set_enc_params(ctx);
+
+ /* pictype : number of B */
+ reg = readl(mfc_regs->e_gop_config);
+ /* num_b_frame - 0 ~ 2 */
+ reg &= ~(0x3 << 16);
+ reg |= (p->num_b_frame << 16);
+ writel(reg, mfc_regs->e_gop_config);
+
+ /* UHD encoding case */
+ if ((ctx->img_width == 3840) && (ctx->img_height == 2160)) {
+ p_hevc->level = 51;
+ p_hevc->tier = 0;
+ /* this tier can be changed */
+ }
+
+ /* tier & level */
+ reg = 0;
+ /* profile */
+ reg |= p_hevc->profile & 0x3;
+ /* level */
+ reg &= ~(0xFF << 8);
+ reg |= (p_hevc->level << 8);
+ /* tier - 0 ~ 1 */
+ reg |= (p_hevc->tier << 16);
+ writel(reg, mfc_regs->e_picture_profile);
+
+ switch (p_hevc->loopfilter) {
+ case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED:
+ p_hevc->loopfilter_disable = 1;
+ break;
+ case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED:
+ p_hevc->loopfilter_disable = 0;
+ p_hevc->loopfilter_across = 1;
+ break;
+ case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY:
+ p_hevc->loopfilter_disable = 0;
+ p_hevc->loopfilter_across = 0;
+ break;
+ }
+
+ /* max partition depth */
+ reg = 0;
+ reg |= (p_hevc->max_partition_depth & 0x1);
+ reg |= (p_hevc->num_refs_for_p-1) << 2;
+ reg |= (p_hevc->refreshtype & 0x3) << 3;
+ reg |= (p_hevc->const_intra_period_enable & 0x1) << 5;
+ reg |= (p_hevc->lossless_cu_enable & 0x1) << 6;
+ reg |= (p_hevc->wavefront_enable & 0x1) << 7;
+ reg |= (p_hevc->loopfilter_disable & 0x1) << 8;
+ reg |= (p_hevc->loopfilter_across & 0x1) << 9;
+ reg |= (p_hevc->enable_ltr & 0x1) << 10;
+ reg |= (p_hevc->hier_qp_enable & 0x1) << 11;
+ reg |= (p_hevc->general_pb_enable & 0x1) << 13;
+ reg |= (p_hevc->temporal_id_enable & 0x1) << 14;
+ reg |= (p_hevc->strong_intra_smooth & 0x1) << 15;
+ reg |= (p_hevc->intra_pu_split_disable & 0x1) << 16;
+ reg |= (p_hevc->tmv_prediction_disable & 0x1) << 17;
+ reg |= (p_hevc->max_num_merge_mv & 0x7) << 18;
+ reg |= (p_hevc->encoding_nostartcode_enable & 0x1) << 23;
+ reg |= (p_hevc->prepend_sps_pps_to_idr << 26);
+
+ writel(reg, mfc_regs->e_hevc_options);
+ /* refresh period */
+ if (p_hevc->refreshtype) {
+ reg = 0;
+ reg |= (p_hevc->refreshperiod & 0xFFFF);
+ writel(reg, mfc_regs->e_hevc_refresh_period);
+ }
+ /* loop filter setting */
+ if (!(p_hevc->loopfilter_disable & 0x1)) {
+ reg = 0;
+ reg |= (p_hevc->lf_beta_offset_div2);
+ writel(reg, mfc_regs->e_hevc_lf_beta_offset_div2);
+ reg = 0;
+ reg |= (p_hevc->lf_tc_offset_div2);
+ writel(reg, mfc_regs->e_hevc_lf_tc_offset_div2);
+ }
+ /* hier qp enable */
+ if (p_hevc->num_hier_layer) {
+ reg = 0;
+ reg |= (p_hevc->hier_qp_type & 0x1) << 0x3;
+ reg |= p_hevc->num_hier_layer & 0x7;
+ writel(reg, mfc_regs->e_num_t_layer);
+ /* QP value for each layer */
+ if (p_hevc->hier_qp_enable) {
+ for (i = 0; i < 7; i++)
+ writel(p_hevc->hier_qp_layer[i],
+ mfc_regs->e_hier_qp_layer0 + i * 4);
+ }
+ if (p->rc_frame) {
+ for (i = 0; i < 7; i++)
+ writel(p_hevc->hier_bit_layer[i],
+ mfc_regs->e_hier_bit_rate_layer0
+ + i * 4);
+ }
+ }
+
+ /* rate control config. */
+ reg = readl(mfc_regs->e_rc_config);
+ /* macroblock level rate control */
+ reg &= ~(0x1 << 8);
+ reg |= (p->rc_mb << 8);
+ writel(reg, mfc_regs->e_rc_config);
+ /* frame QP */
+ reg &= ~(0xFF);
+ reg |= p_hevc->rc_frame_qp;
+ writel(reg, mfc_regs->e_rc_config);
+
+ /* frame rate */
+ if (p->rc_frame) {
+ reg = 0;
+ reg &= ~(0xFFFF << 16);
+ reg |= ((p_hevc->rc_framerate) << 16);
+ reg &= ~(0xFFFF);
+ reg |= FRAME_DELTA_DEFAULT;
+ writel(reg, mfc_regs->e_rc_frame_rate);
+ }
+
+ /* max & min value of QP */
+ reg = 0;
+ /* max QP */
+ reg &= ~(0xFF << 8);
+ reg |= (p_hevc->rc_max_qp << 8);
+ /* min QP */
+ reg &= ~(0xFF);
+ reg |= p_hevc->rc_min_qp;
+ writel(reg, mfc_regs->e_rc_qp_bound);
+
+ writel(0x0, mfc_regs->e_fixed_picture_qp);
+ if (!p->rc_frame && !p->rc_mb) {
+ reg = 0;
+ reg &= ~(0xFF << 16);
+ reg |= (p_hevc->rc_b_frame_qp << 16);
+ reg &= ~(0xFF << 8);
+ reg |= (p_hevc->rc_p_frame_qp << 8);
+ reg &= ~(0xFF);
+ reg |= p_hevc->rc_frame_qp;
+ writel(reg, mfc_regs->e_fixed_picture_qp);
+ }
+ mfc_debug_leave();
+
+ return 0;
+}
+
+/* Initialize decoding */
+static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ unsigned int reg = 0;
+ int fmo_aso_ctrl = 0;
+
+ mfc_debug_enter();
+ mfc_debug(2, "InstNo: %d/%d\n", ctx->inst_no,
+ S5P_FIMV_CH_SEQ_HEADER_V6);
+ mfc_debug(2, "BUFs: %08x %08x %08x\n",
+ readl(mfc_regs->d_cpb_buffer_addr),
+ readl(mfc_regs->d_cpb_buffer_addr),
+ readl(mfc_regs->d_cpb_buffer_addr));
+
+ /* FMO_ASO_CTRL - 0: Enable, 1: Disable */
+ reg |= (fmo_aso_ctrl << S5P_FIMV_D_OPT_FMO_ASO_CTRL_MASK_V6);
+
+ if (ctx->display_delay_enable) {
+ reg |= (0x1 << S5P_FIMV_D_OPT_DDELAY_EN_SHIFT_V6);
+ writel(ctx->display_delay, mfc_regs->d_display_delay);
+ }
+
+ if (IS_MFCV7_PLUS(dev) || IS_MFCV6_V2(dev)) {
+ writel(reg, mfc_regs->d_dec_options);
+ reg = 0;
+ }
+
+ /* Setup loop filter, for decoding this is only valid for MPEG4 */
+ if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_DEC) {
+ mfc_debug(2, "Set loop filter to: %d\n",
+ ctx->loop_filter_mpeg4);
+ reg |= (ctx->loop_filter_mpeg4 <<
+ S5P_FIMV_D_OPT_LF_CTRL_SHIFT_V6);
+ }
+ if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16)
+ reg |= (0x1 << S5P_FIMV_D_OPT_TILE_MODE_SHIFT_V6);
+
+ if (IS_MFCV7_PLUS(dev) || IS_MFCV6_V2(dev))
+ writel(reg, mfc_regs->d_init_buffer_options);
+ else
+ writel(reg, mfc_regs->d_dec_options);
+
+ /* 0: NV12(CbCr), 1: NV21(CrCb) */
+ if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV21M)
+ writel(0x1, mfc_regs->pixel_format);
+ else
+ writel(0x0, mfc_regs->pixel_format);
+
+
+ /* sei parse */
+ writel(ctx->sei_fp_parse & 0x1, mfc_regs->d_sei_enable);
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_SEQ_HEADER_V6, NULL);
+
+ mfc_debug_leave();
+ return 0;
+}
+
+static inline void s5p_mfc_set_flush(struct s5p_mfc_ctx *ctx, int flush)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ if (flush) {
+ dev->curr_ctx = ctx->num;
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_H2R_CMD_FLUSH_V6, NULL);
+ }
+}
+
+/* Decode a single frame */
+static int s5p_mfc_decode_one_frame_v6(struct s5p_mfc_ctx *ctx,
+ enum s5p_mfc_decode_arg last_frame)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ writel(ctx->dec_dst_flag, mfc_regs->d_available_dpb_flag_lower);
+ writel(ctx->slice_interface & 0x1, mfc_regs->d_slice_if_enable);
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ /* Issue different commands to instance basing on whether it
+ * is the last frame or not. */
+ switch (last_frame) {
+ case 0:
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_FRAME_START_V6, NULL);
+ break;
+ case 1:
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_LAST_FRAME_V6, NULL);
+ break;
+ default:
+ mfc_err("Unsupported last frame arg.\n");
+ return -EINVAL;
+ }
+
+ mfc_debug(2, "Decoding a usual frame.\n");
+ return 0;
+}
+
+static int s5p_mfc_init_encode_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC)
+ s5p_mfc_set_enc_params_h264(ctx);
+ else if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_ENC)
+ s5p_mfc_set_enc_params_mpeg4(ctx);
+ else if (ctx->codec_mode == S5P_MFC_CODEC_H263_ENC)
+ s5p_mfc_set_enc_params_h263(ctx);
+ else if (ctx->codec_mode == S5P_MFC_CODEC_VP8_ENC)
+ s5p_mfc_set_enc_params_vp8(ctx);
+ else if (ctx->codec_mode == S5P_FIMV_CODEC_HEVC_ENC)
+ s5p_mfc_set_enc_params_hevc(ctx);
+ else {
+ mfc_err("Unknown codec for encoding (%x).\n",
+ ctx->codec_mode);
+ return -EINVAL;
+ }
+
+ /* Set stride lengths for v7 & above */
+ if (IS_MFCV7_PLUS(dev)) {
+ writel(ctx->img_width, mfc_regs->e_source_first_plane_stride);
+ writel(ctx->img_width, mfc_regs->e_source_second_plane_stride);
+ }
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev,
+ S5P_FIMV_CH_SEQ_HEADER_V6, NULL);
+
+ return 0;
+}
+
+static int s5p_mfc_h264_set_aso_slice_order_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ struct s5p_mfc_enc_params *p = &ctx->enc_params;
+ struct s5p_mfc_h264_enc_params *p_h264 = &p->codec.h264;
+ int i;
+
+ if (p_h264->aso) {
+ for (i = 0; i < ARRAY_SIZE(p_h264->aso_slice_order); i++) {
+ writel(p_h264->aso_slice_order[i],
+ mfc_regs->e_h264_aso_slice_order_0 + i * 4);
+ }
+ }
+ return 0;
+}
+
+/* Encode a single frame */
+static int s5p_mfc_encode_one_frame_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ int cmd;
+
+ mfc_debug(2, "++\n");
+
+ /* memory structure cur. frame */
+
+ if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC)
+ s5p_mfc_h264_set_aso_slice_order_v6(ctx);
+
+ s5p_mfc_set_slice_mode(ctx);
+
+ if (ctx->state != MFCINST_FINISHING)
+ cmd = S5P_FIMV_CH_FRAME_START_V6;
+ else
+ cmd = S5P_FIMV_CH_LAST_FRAME_V6;
+
+ writel(ctx->inst_no, mfc_regs->instance_id);
+ s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, cmd, NULL);
+
+ mfc_debug(2, "--\n");
+
+ return 0;
+}
+
+static inline void s5p_mfc_run_dec_last_frames(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+
+ s5p_mfc_set_dec_stream_buffer_v6(ctx, 0, 0, 0);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_decode_one_frame_v6(ctx, MFC_DEC_LAST_FRAME);
+}
+
+static inline int s5p_mfc_run_dec_frame(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *temp_vb;
+ int last_frame = 0;
+
+ if (ctx->state == MFCINST_FINISHING) {
+ last_frame = MFC_DEC_LAST_FRAME;
+ s5p_mfc_set_dec_stream_buffer_v6(ctx, 0, 0, 0);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_clean_ctx_int_flags(ctx);
+ s5p_mfc_decode_one_frame_v6(ctx, last_frame);
+ return 0;
+ }
+
+ /* Frames are being decoded */
+ if (list_empty(&ctx->src_queue)) {
+ mfc_debug(2, "No src buffers.\n");
+ return -EAGAIN;
+ }
+ /* Get the next source buffer */
+ temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ temp_vb->flags |= MFC_BUF_FLAG_USED;
+ s5p_mfc_set_dec_stream_buffer_v6(ctx,
+ vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0),
+ ctx->consumed_stream,
+ temp_vb->b->vb2_buf.planes[0].bytesused);
+
+ dev->curr_ctx = ctx->num;
+ if (temp_vb->b->vb2_buf.planes[0].bytesused == 0) {
+ last_frame = 1;
+ mfc_debug(2, "Setting ctx->state to FINISHING\n");
+ ctx->state = MFCINST_FINISHING;
+ }
+ s5p_mfc_decode_one_frame_v6(ctx, last_frame);
+
+ return 0;
+}
+
+static inline int s5p_mfc_run_enc_frame(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ struct s5p_mfc_buf *src_mb;
+ unsigned long src_y_addr, src_c_addr, dst_addr;
+ /*
+ unsigned int src_y_size, src_c_size;
+ */
+ unsigned int dst_size;
+
+ if (list_empty(&ctx->src_queue) && ctx->state != MFCINST_FINISHING) {
+ mfc_debug(2, "no src buffers.\n");
+ return -EAGAIN;
+ }
+
+ if (list_empty(&ctx->dst_queue)) {
+ mfc_debug(2, "no dst buffers.\n");
+ return -EAGAIN;
+ }
+
+ if (list_empty(&ctx->src_queue)) {
+ /* send null frame */
+ s5p_mfc_set_enc_frame_buffer_v6(ctx, 0, 0);
+ src_mb = NULL;
+ } else {
+ src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ src_mb->flags |= MFC_BUF_FLAG_USED;
+ if (src_mb->b->vb2_buf.planes[0].bytesused == 0) {
+ s5p_mfc_set_enc_frame_buffer_v6(ctx, 0, 0);
+ ctx->state = MFCINST_FINISHING;
+ } else {
+ src_y_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 0);
+ src_c_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 1);
+
+ mfc_debug(2, "enc src y addr: 0x%08lx\n", src_y_addr);
+ mfc_debug(2, "enc src c addr: 0x%08lx\n", src_c_addr);
+
+ s5p_mfc_set_enc_frame_buffer_v6(ctx, src_y_addr, src_c_addr);
+ if (src_mb->flags & MFC_BUF_FLAG_EOS)
+ ctx->state = MFCINST_FINISHING;
+ }
+ }
+
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_mb->flags |= MFC_BUF_FLAG_USED;
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+
+ s5p_mfc_set_enc_stream_buffer_v6(ctx, dst_addr, dst_size);
+
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_encode_one_frame_v6(ctx);
+
+ return 0;
+}
+
+static inline void s5p_mfc_run_init_dec(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *temp_vb;
+
+ /* Initializing decoding - parsing header */
+ mfc_debug(2, "Preparing to init decoding.\n");
+ temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
+ mfc_debug(2, "Header size: %d\n", temp_vb->b->vb2_buf.planes[0].bytesused);
+ s5p_mfc_set_dec_stream_buffer_v6(ctx,
+ vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), 0,
+ temp_vb->b->vb2_buf.planes[0].bytesused);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_init_decode_v6(ctx);
+}
+
+static inline void s5p_mfc_run_init_enc(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ struct s5p_mfc_buf *dst_mb;
+ unsigned long dst_addr;
+ unsigned int dst_size;
+
+ dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list);
+ dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0);
+ dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0);
+ s5p_mfc_set_enc_stream_buffer_v6(ctx, dst_addr, dst_size);
+ dev->curr_ctx = ctx->num;
+ s5p_mfc_init_encode_v6(ctx);
+}
+
+static inline int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ int ret;
+ /* Header was parsed now start processing
+ * First set the output frame buffers
+ * s5p_mfc_alloc_dec_buffers(ctx); */
+
+ if (ctx->capture_state != QUEUE_BUFS_MMAPED) {
+ mfc_err("It seems that not all destination buffers were\n"
+ "mmapped.MFC requires that all destination are mmapped\n"
+ "before starting processing.\n");
+ return -EAGAIN;
+ }
+
+ dev->curr_ctx = ctx->num;
+ ret = s5p_mfc_set_dec_frame_buffer_v6(ctx);
+ if (ret) {
+ mfc_err("Failed to alloc frame mem.\n");
+ ctx->state = MFCINST_ERROR;
+ }
+ return ret;
+}
+
+static inline int s5p_mfc_run_init_enc_buffers(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ int ret;
+
+ dev->curr_ctx = ctx->num;
+ ret = s5p_mfc_set_enc_ref_buffer_v6(ctx);
+ if (ret) {
+ mfc_err("Failed to alloc frame mem.\n");
+ ctx->state = MFCINST_ERROR;
+ }
+ return ret;
+}
+
+/* Try running an operation on hardware */
+static void s5p_mfc_try_run_v6(struct s5p_mfc_dev *dev)
+{
+ struct s5p_mfc_ctx *ctx;
+ int new_ctx;
+ unsigned int ret = 0;
+
+ mfc_debug(1, "Try run dev: %p\n", dev);
+
+ /* Check whether hardware is not running */
+ if (test_and_set_bit(0, &dev->hw_lock) != 0) {
+ /* This is perfectly ok, the scheduled ctx should wait */
+ mfc_debug(1, "Couldn't lock HW.\n");
+ return;
+ }
+
+ /* Choose the context to run */
+ new_ctx = s5p_mfc_get_new_ctx(dev);
+ if (new_ctx < 0) {
+ /* No contexts to run */
+ if (test_and_clear_bit(0, &dev->hw_lock) == 0) {
+ mfc_err("Failed to unlock hardware.\n");
+ return;
+ }
+
+ mfc_debug(1, "No ctx is scheduled to be run.\n");
+ return;
+ }
+
+ mfc_debug(1, "New context: %d\n", new_ctx);
+ ctx = dev->ctx[new_ctx];
+ mfc_debug(1, "Setting new context to %p\n", ctx);
+ /* Got context to run in ctx */
+ mfc_debug(1, "ctx->dst_queue_cnt=%d ctx->dpb_count=%d ctx->src_queue_cnt=%d\n",
+ ctx->dst_queue_cnt, ctx->pb_count, ctx->src_queue_cnt);
+ mfc_debug(1, "ctx->state=%d\n", ctx->state);
+ /* Last frame has already been sent to MFC
+ * Now obtaining frames from MFC buffer */
+
+ s5p_mfc_clock_on();
+ s5p_mfc_clean_ctx_int_flags(ctx);
+
+ if (ctx->type == MFCINST_DECODER) {
+ switch (ctx->state) {
+ case MFCINST_FINISHING:
+ s5p_mfc_run_dec_last_frames(ctx);
+ break;
+ case MFCINST_RUNNING:
+ ret = s5p_mfc_run_dec_frame(ctx);
+ break;
+ case MFCINST_INIT:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_RETURN_INST:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_GOT_INST:
+ s5p_mfc_run_init_dec(ctx);
+ break;
+ case MFCINST_HEAD_PARSED:
+ ret = s5p_mfc_run_init_dec_buffers(ctx);
+ break;
+ case MFCINST_FLUSH:
+ s5p_mfc_set_flush(ctx, ctx->dpb_flush_flag);
+ break;
+ case MFCINST_RES_CHANGE_INIT:
+ s5p_mfc_run_dec_last_frames(ctx);
+ break;
+ case MFCINST_RES_CHANGE_FLUSH:
+ s5p_mfc_run_dec_last_frames(ctx);
+ break;
+ case MFCINST_RES_CHANGE_END:
+ mfc_debug(2, "Finished remaining frames after resolution change.\n");
+ ctx->capture_state = QUEUE_FREE;
+ mfc_debug(2, "Will re-init the codec`.\n");
+ s5p_mfc_run_init_dec(ctx);
+ break;
+ default:
+ ret = -EAGAIN;
+ }
+ } else if (ctx->type == MFCINST_ENCODER) {
+ switch (ctx->state) {
+ case MFCINST_FINISHING:
+ case MFCINST_RUNNING:
+ ret = s5p_mfc_run_enc_frame(ctx);
+ break;
+ case MFCINST_INIT:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_RETURN_INST:
+ ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd,
+ ctx);
+ break;
+ case MFCINST_GOT_INST:
+ s5p_mfc_run_init_enc(ctx);
+ break;
+ case MFCINST_HEAD_PRODUCED:
+ ret = s5p_mfc_run_init_enc_buffers(ctx);
+ break;
+ default:
+ ret = -EAGAIN;
+ }
+ } else {
+ mfc_err("invalid context type: %d\n", ctx->type);
+ ret = -EAGAIN;
+ }
+
+ if (ret) {
+ /* Free hardware lock */
+ if (test_and_clear_bit(0, &dev->hw_lock) == 0)
+ mfc_err("Failed to unlock hardware.\n");
+
+ /* This is in deed imporant, as no operation has been
+ * scheduled, reduce the clock count as no one will
+ * ever do this, because no interrupt related to this try_run
+ * will ever come from hardware. */
+ s5p_mfc_clock_off();
+ }
+}
+
+static void s5p_mfc_clear_int_flags_v6(struct s5p_mfc_dev *dev)
+{
+ const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs;
+ writel(0, mfc_regs->risc2host_command);
+ writel(0, mfc_regs->risc2host_int);
+}
+
+static unsigned int
+s5p_mfc_read_info_v6(struct s5p_mfc_ctx *ctx, unsigned long ofs)
+{
+ int ret;
+
+ s5p_mfc_clock_on();
+ ret = readl((void __iomem *)ofs);
+ s5p_mfc_clock_off();
+
+ return ret;
+}
+
+static int s5p_mfc_get_dspl_y_adr_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_display_first_plane_addr);
+}
+
+static int s5p_mfc_get_dec_y_adr_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_decoded_first_plane_addr);
+}
+
+static int s5p_mfc_get_dspl_status_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_display_status);
+}
+
+static int s5p_mfc_get_dec_status_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_decoded_status);
+}
+
+static int s5p_mfc_get_dec_frame_type_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_decoded_frame_type) &
+ S5P_FIMV_DECODE_FRAME_MASK_V6;
+}
+
+static int s5p_mfc_get_disp_frame_type_v6(struct s5p_mfc_ctx *ctx)
+{
+ struct s5p_mfc_dev *dev = ctx->dev;
+ return readl(dev->mfc_regs->d_display_frame_type) &
+ S5P_FIMV_DECODE_FRAME_MASK_V6;
+}
+
+static int s5p_mfc_get_consumed_stream_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_decoded_nal_size);
+}
+
+static int s5p_mfc_get_int_reason_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->risc2host_command) &
+ S5P_FIMV_RISC2HOST_CMD_MASK;
+}
+
+static int s5p_mfc_get_int_err_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->error_code);
+}
+
+static int s5p_mfc_err_dec_v6(unsigned int err)
+{
+ return (err & S5P_FIMV_ERR_DEC_MASK_V6) >> S5P_FIMV_ERR_DEC_SHIFT_V6;
+}
+
+static int s5p_mfc_get_img_width_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_display_frame_width);
+}
+
+static int s5p_mfc_get_img_height_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_display_frame_height);
+}
+
+static int s5p_mfc_get_dpb_count_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_min_num_dpb);
+}
+
+static int s5p_mfc_get_mv_count_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_min_num_mv);
+}
+
+static int s5p_mfc_get_min_scratch_buf_size(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->d_min_scratch_buffer_size);
+}
+
+static int s5p_mfc_get_e_min_scratch_buf_size(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->e_min_scratch_buffer_size);
+}
+
+static int s5p_mfc_get_inst_no_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->ret_instance_id);
+}
+
+static int s5p_mfc_get_enc_dpb_count_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->e_num_dpb);
+}
+
+static int s5p_mfc_get_enc_strm_size_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->e_stream_size);
+}
+
+static int s5p_mfc_get_enc_slice_type_v6(struct s5p_mfc_dev *dev)
+{
+ return readl(dev->mfc_regs->e_slice_type);
+}
+
+static unsigned int s5p_mfc_get_pic_type_top_v6(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v6(ctx,
+ (__force unsigned long) ctx->dev->mfc_regs->d_ret_picture_tag_top);
+}
+
+static unsigned int s5p_mfc_get_pic_type_bot_v6(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v6(ctx,
+ (__force unsigned long) ctx->dev->mfc_regs->d_ret_picture_tag_bot);
+}
+
+static unsigned int s5p_mfc_get_crop_info_h_v6(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v6(ctx,
+ (__force unsigned long) ctx->dev->mfc_regs->d_display_crop_info1);
+}
+
+static unsigned int s5p_mfc_get_crop_info_v_v6(struct s5p_mfc_ctx *ctx)
+{
+ return s5p_mfc_read_info_v6(ctx,
+ (__force unsigned long) ctx->dev->mfc_regs->d_display_crop_info2);
+}
+
+static struct s5p_mfc_regs mfc_regs;
+
+/* Initialize registers for MFC v6 onwards */
+const struct s5p_mfc_regs *s5p_mfc_init_regs_v6_plus(struct s5p_mfc_dev *dev)
+{
+ memset(&mfc_regs, 0, sizeof(mfc_regs));
+
+#define S5P_MFC_REG_ADDR(dev, reg) ((dev)->regs_base + (reg))
+#define R(m, r) mfc_regs.m = S5P_MFC_REG_ADDR(dev, r)
+ /* codec common registers */
+ R(risc_on, S5P_FIMV_RISC_ON_V6);
+ R(risc2host_int, S5P_FIMV_RISC2HOST_INT_V6);
+ R(host2risc_int, S5P_FIMV_HOST2RISC_INT_V6);
+ R(risc_base_address, S5P_FIMV_RISC_BASE_ADDRESS_V6);
+ R(mfc_reset, S5P_FIMV_MFC_RESET_V6);
+ R(host2risc_command, S5P_FIMV_HOST2RISC_CMD_V6);
+ R(risc2host_command, S5P_FIMV_RISC2HOST_CMD_V6);
+ R(firmware_version, S5P_FIMV_FW_VERSION_V6);
+ R(instance_id, S5P_FIMV_INSTANCE_ID_V6);
+ R(codec_type, S5P_FIMV_CODEC_TYPE_V6);
+ R(context_mem_addr, S5P_FIMV_CONTEXT_MEM_ADDR_V6);
+ R(context_mem_size, S5P_FIMV_CONTEXT_MEM_SIZE_V6);
+ R(pixel_format, S5P_FIMV_PIXEL_FORMAT_V6);
+ R(ret_instance_id, S5P_FIMV_RET_INSTANCE_ID_V6);
+ R(error_code, S5P_FIMV_ERROR_CODE_V6);
+
+ /* decoder registers */
+ R(d_crc_ctrl, S5P_FIMV_D_CRC_CTRL_V6);
+ R(d_dec_options, S5P_FIMV_D_DEC_OPTIONS_V6);
+ R(d_display_delay, S5P_FIMV_D_DISPLAY_DELAY_V6);
+ R(d_sei_enable, S5P_FIMV_D_SEI_ENABLE_V6);
+ R(d_min_num_dpb, S5P_FIMV_D_MIN_NUM_DPB_V6);
+ R(d_min_num_mv, S5P_FIMV_D_MIN_NUM_MV_V6);
+ R(d_mvc_num_views, S5P_FIMV_D_MVC_NUM_VIEWS_V6);
+ R(d_num_dpb, S5P_FIMV_D_NUM_DPB_V6);
+ R(d_num_mv, S5P_FIMV_D_NUM_MV_V6);
+ R(d_init_buffer_options, S5P_FIMV_D_INIT_BUFFER_OPTIONS_V6);
+ R(d_first_plane_dpb_size, S5P_FIMV_D_LUMA_DPB_SIZE_V6);
+ R(d_second_plane_dpb_size, S5P_FIMV_D_CHROMA_DPB_SIZE_V6);
+ R(d_mv_buffer_size, S5P_FIMV_D_MV_BUFFER_SIZE_V6);
+ R(d_first_plane_dpb, S5P_FIMV_D_LUMA_DPB_V6);
+ R(d_second_plane_dpb, S5P_FIMV_D_CHROMA_DPB_V6);
+ R(d_mv_buffer, S5P_FIMV_D_MV_BUFFER_V6);
+ R(d_scratch_buffer_addr, S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V6);
+ R(d_scratch_buffer_size, S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V6);
+ R(d_cpb_buffer_addr, S5P_FIMV_D_CPB_BUFFER_ADDR_V6);
+ R(d_cpb_buffer_size, S5P_FIMV_D_CPB_BUFFER_SIZE_V6);
+ R(d_available_dpb_flag_lower, S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V6);
+ R(d_cpb_buffer_offset, S5P_FIMV_D_CPB_BUFFER_OFFSET_V6);
+ R(d_slice_if_enable, S5P_FIMV_D_SLICE_IF_ENABLE_V6);
+ R(d_stream_data_size, S5P_FIMV_D_STREAM_DATA_SIZE_V6);
+ R(d_display_frame_width, S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V6);
+ R(d_display_frame_height, S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V6);
+ R(d_display_status, S5P_FIMV_D_DISPLAY_STATUS_V6);
+ R(d_display_first_plane_addr, S5P_FIMV_D_DISPLAY_LUMA_ADDR_V6);
+ R(d_display_second_plane_addr, S5P_FIMV_D_DISPLAY_CHROMA_ADDR_V6);
+ R(d_display_frame_type, S5P_FIMV_D_DISPLAY_FRAME_TYPE_V6);
+ R(d_display_crop_info1, S5P_FIMV_D_DISPLAY_CROP_INFO1_V6);
+ R(d_display_crop_info2, S5P_FIMV_D_DISPLAY_CROP_INFO2_V6);
+ R(d_display_aspect_ratio, S5P_FIMV_D_DISPLAY_ASPECT_RATIO_V6);
+ R(d_display_extended_ar, S5P_FIMV_D_DISPLAY_EXTENDED_AR_V6);
+ R(d_decoded_status, S5P_FIMV_D_DECODED_STATUS_V6);
+ R(d_decoded_first_plane_addr, S5P_FIMV_D_DECODED_LUMA_ADDR_V6);
+ R(d_decoded_second_plane_addr, S5P_FIMV_D_DECODED_CHROMA_ADDR_V6);
+ R(d_decoded_frame_type, S5P_FIMV_D_DECODED_FRAME_TYPE_V6);
+ R(d_decoded_nal_size, S5P_FIMV_D_DECODED_NAL_SIZE_V6);
+ R(d_ret_picture_tag_top, S5P_FIMV_D_RET_PICTURE_TAG_TOP_V6);
+ R(d_ret_picture_tag_bot, S5P_FIMV_D_RET_PICTURE_TAG_BOT_V6);
+ R(d_h264_info, S5P_FIMV_D_H264_INFO_V6);
+ R(d_mvc_view_id, S5P_FIMV_D_MVC_VIEW_ID_V6);
+ R(d_frame_pack_sei_avail, S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V6);
+
+ /* encoder registers */
+ R(e_frame_width, S5P_FIMV_E_FRAME_WIDTH_V6);
+ R(e_frame_height, S5P_FIMV_E_FRAME_HEIGHT_V6);
+ R(e_cropped_frame_width, S5P_FIMV_E_CROPPED_FRAME_WIDTH_V6);
+ R(e_cropped_frame_height, S5P_FIMV_E_CROPPED_FRAME_HEIGHT_V6);
+ R(e_frame_crop_offset, S5P_FIMV_E_FRAME_CROP_OFFSET_V6);
+ R(e_enc_options, S5P_FIMV_E_ENC_OPTIONS_V6);
+ R(e_picture_profile, S5P_FIMV_E_PICTURE_PROFILE_V6);
+ R(e_vbv_buffer_size, S5P_FIMV_E_VBV_BUFFER_SIZE_V6);
+ R(e_vbv_init_delay, S5P_FIMV_E_VBV_INIT_DELAY_V6);
+ R(e_fixed_picture_qp, S5P_FIMV_E_FIXED_PICTURE_QP_V6);
+ R(e_rc_config, S5P_FIMV_E_RC_CONFIG_V6);
+ R(e_rc_qp_bound, S5P_FIMV_E_RC_QP_BOUND_V6);
+ R(e_rc_mode, S5P_FIMV_E_RC_RPARAM_V6);
+ R(e_mb_rc_config, S5P_FIMV_E_MB_RC_CONFIG_V6);
+ R(e_padding_ctrl, S5P_FIMV_E_PADDING_CTRL_V6);
+ R(e_mv_hor_range, S5P_FIMV_E_MV_HOR_RANGE_V6);
+ R(e_mv_ver_range, S5P_FIMV_E_MV_VER_RANGE_V6);
+ R(e_num_dpb, S5P_FIMV_E_NUM_DPB_V6);
+ R(e_luma_dpb, S5P_FIMV_E_LUMA_DPB_V6);
+ R(e_chroma_dpb, S5P_FIMV_E_CHROMA_DPB_V6);
+ R(e_me_buffer, S5P_FIMV_E_ME_BUFFER_V6);
+ R(e_scratch_buffer_addr, S5P_FIMV_E_SCRATCH_BUFFER_ADDR_V6);
+ R(e_scratch_buffer_size, S5P_FIMV_E_SCRATCH_BUFFER_SIZE_V6);
+ R(e_tmv_buffer0, S5P_FIMV_E_TMV_BUFFER0_V6);
+ R(e_tmv_buffer1, S5P_FIMV_E_TMV_BUFFER1_V6);
+ R(e_source_first_plane_addr, S5P_FIMV_E_SOURCE_LUMA_ADDR_V6);
+ R(e_source_second_plane_addr, S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6);
+ R(e_stream_buffer_addr, S5P_FIMV_E_STREAM_BUFFER_ADDR_V6);
+ R(e_stream_buffer_size, S5P_FIMV_E_STREAM_BUFFER_SIZE_V6);
+ R(e_roi_buffer_addr, S5P_FIMV_E_ROI_BUFFER_ADDR_V6);
+ R(e_param_change, S5P_FIMV_E_PARAM_CHANGE_V6);
+ R(e_ir_size, S5P_FIMV_E_IR_SIZE_V6);
+ R(e_gop_config, S5P_FIMV_E_GOP_CONFIG_V6);
+ R(e_mslice_mode, S5P_FIMV_E_MSLICE_MODE_V6);
+ R(e_mslice_size_mb, S5P_FIMV_E_MSLICE_SIZE_MB_V6);
+ R(e_mslice_size_bits, S5P_FIMV_E_MSLICE_SIZE_BITS_V6);
+ R(e_frame_insertion, S5P_FIMV_E_FRAME_INSERTION_V6);
+ R(e_rc_frame_rate, S5P_FIMV_E_RC_FRAME_RATE_V6);
+ R(e_rc_bit_rate, S5P_FIMV_E_RC_BIT_RATE_V6);
+ R(e_rc_roi_ctrl, S5P_FIMV_E_RC_ROI_CTRL_V6);
+ R(e_picture_tag, S5P_FIMV_E_PICTURE_TAG_V6);
+ R(e_bit_count_enable, S5P_FIMV_E_BIT_COUNT_ENABLE_V6);
+ R(e_max_bit_count, S5P_FIMV_E_MAX_BIT_COUNT_V6);
+ R(e_min_bit_count, S5P_FIMV_E_MIN_BIT_COUNT_V6);
+ R(e_metadata_buffer_addr, S5P_FIMV_E_METADATA_BUFFER_ADDR_V6);
+ R(e_metadata_buffer_size, S5P_FIMV_E_METADATA_BUFFER_SIZE_V6);
+ R(e_encoded_source_first_plane_addr,
+ S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6);
+ R(e_encoded_source_second_plane_addr,
+ S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6);
+ R(e_stream_size, S5P_FIMV_E_STREAM_SIZE_V6);
+ R(e_slice_type, S5P_FIMV_E_SLICE_TYPE_V6);
+ R(e_picture_count, S5P_FIMV_E_PICTURE_COUNT_V6);
+ R(e_ret_picture_tag, S5P_FIMV_E_RET_PICTURE_TAG_V6);
+ R(e_recon_luma_dpb_addr, S5P_FIMV_E_RECON_LUMA_DPB_ADDR_V6);
+ R(e_recon_chroma_dpb_addr, S5P_FIMV_E_RECON_CHROMA_DPB_ADDR_V6);
+ R(e_mpeg4_options, S5P_FIMV_E_MPEG4_OPTIONS_V6);
+ R(e_mpeg4_hec_period, S5P_FIMV_E_MPEG4_HEC_PERIOD_V6);
+ R(e_aspect_ratio, S5P_FIMV_E_ASPECT_RATIO_V6);
+ R(e_extended_sar, S5P_FIMV_E_EXTENDED_SAR_V6);
+ R(e_h264_options, S5P_FIMV_E_H264_OPTIONS_V6);
+ R(e_h264_lf_alpha_offset, S5P_FIMV_E_H264_LF_ALPHA_OFFSET_V6);
+ R(e_h264_lf_beta_offset, S5P_FIMV_E_H264_LF_BETA_OFFSET_V6);
+ R(e_h264_i_period, S5P_FIMV_E_H264_I_PERIOD_V6);
+ R(e_h264_fmo_slice_grp_map_type,
+ S5P_FIMV_E_H264_FMO_SLICE_GRP_MAP_TYPE_V6);
+ R(e_h264_fmo_num_slice_grp_minus1,
+ S5P_FIMV_E_H264_FMO_NUM_SLICE_GRP_MINUS1_V6);
+ R(e_h264_fmo_slice_grp_change_dir,
+ S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_DIR_V6);
+ R(e_h264_fmo_slice_grp_change_rate_minus1,
+ S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_RATE_MINUS1_V6);
+ R(e_h264_fmo_run_length_minus1_0,
+ S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_0_V6);
+ R(e_h264_aso_slice_order_0, S5P_FIMV_E_H264_ASO_SLICE_ORDER_0_V6);
+ R(e_h264_num_t_layer, S5P_FIMV_E_H264_NUM_T_LAYER_V6);
+ R(e_h264_hierarchical_qp_layer0,
+ S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER0_V6);
+ R(e_h264_frame_packing_sei_info,
+ S5P_FIMV_E_H264_FRAME_PACKING_SEI_INFO_V6);
+
+ if (!IS_MFCV7_PLUS(dev))
+ goto done;
+
+ /* Initialize registers used in MFC v7+ */
+ R(e_source_first_plane_addr, S5P_FIMV_E_SOURCE_FIRST_ADDR_V7);
+ R(e_source_second_plane_addr, S5P_FIMV_E_SOURCE_SECOND_ADDR_V7);
+ R(e_source_third_plane_addr, S5P_FIMV_E_SOURCE_THIRD_ADDR_V7);
+ R(e_source_first_plane_stride, S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7);
+ R(e_source_second_plane_stride, S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7);
+ R(e_source_third_plane_stride, S5P_FIMV_E_SOURCE_THIRD_STRIDE_V7);
+ R(e_encoded_source_first_plane_addr,
+ S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7);
+ R(e_encoded_source_second_plane_addr,
+ S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7);
+ R(e_vp8_options, S5P_FIMV_E_VP8_OPTIONS_V7);
+
+ if (!IS_MFCV8_PLUS(dev))
+ goto done;
+
+ /* Initialize registers used in MFC v8 only.
+ * Also, over-write the registers which have
+ * a different offset for MFC v8. */
+ R(d_stream_data_size, S5P_FIMV_D_STREAM_DATA_SIZE_V8);
+ R(d_cpb_buffer_addr, S5P_FIMV_D_CPB_BUFFER_ADDR_V8);
+ R(d_cpb_buffer_size, S5P_FIMV_D_CPB_BUFFER_SIZE_V8);
+ R(d_cpb_buffer_offset, S5P_FIMV_D_CPB_BUFFER_OFFSET_V8);
+ R(d_first_plane_dpb_size, S5P_FIMV_D_FIRST_PLANE_DPB_SIZE_V8);
+ R(d_second_plane_dpb_size, S5P_FIMV_D_SECOND_PLANE_DPB_SIZE_V8);
+ R(d_scratch_buffer_addr, S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V8);
+ R(d_scratch_buffer_size, S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V8);
+ R(d_first_plane_dpb_stride_size,
+ S5P_FIMV_D_FIRST_PLANE_DPB_STRIDE_SIZE_V8);
+ R(d_second_plane_dpb_stride_size,
+ S5P_FIMV_D_SECOND_PLANE_DPB_STRIDE_SIZE_V8);
+ R(d_mv_buffer_size, S5P_FIMV_D_MV_BUFFER_SIZE_V8);
+ R(d_num_mv, S5P_FIMV_D_NUM_MV_V8);
+ R(d_first_plane_dpb, S5P_FIMV_D_FIRST_PLANE_DPB_V8);
+ R(d_second_plane_dpb, S5P_FIMV_D_SECOND_PLANE_DPB_V8);
+ R(d_mv_buffer, S5P_FIMV_D_MV_BUFFER_V8);
+ R(d_init_buffer_options, S5P_FIMV_D_INIT_BUFFER_OPTIONS_V8);
+ R(d_available_dpb_flag_lower, S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V8);
+ R(d_slice_if_enable, S5P_FIMV_D_SLICE_IF_ENABLE_V8);
+ R(d_display_first_plane_addr, S5P_FIMV_D_DISPLAY_FIRST_PLANE_ADDR_V8);
+ R(d_display_second_plane_addr, S5P_FIMV_D_DISPLAY_SECOND_PLANE_ADDR_V8);
+ R(d_decoded_first_plane_addr, S5P_FIMV_D_DECODED_FIRST_PLANE_ADDR_V8);
+ R(d_decoded_second_plane_addr, S5P_FIMV_D_DECODED_SECOND_PLANE_ADDR_V8);
+ R(d_display_status, S5P_FIMV_D_DISPLAY_STATUS_V8);
+ R(d_decoded_status, S5P_FIMV_D_DECODED_STATUS_V8);
+ R(d_decoded_frame_type, S5P_FIMV_D_DECODED_FRAME_TYPE_V8);
+ R(d_display_frame_type, S5P_FIMV_D_DISPLAY_FRAME_TYPE_V8);
+ R(d_decoded_nal_size, S5P_FIMV_D_DECODED_NAL_SIZE_V8);
+ R(d_display_frame_width, S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V8);
+ R(d_display_frame_height, S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V8);
+ R(d_frame_pack_sei_avail, S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V8);
+ R(d_mvc_num_views, S5P_FIMV_D_MVC_NUM_VIEWS_V8);
+ R(d_mvc_view_id, S5P_FIMV_D_MVC_VIEW_ID_V8);
+ R(d_ret_picture_tag_top, S5P_FIMV_D_RET_PICTURE_TAG_TOP_V8);
+ R(d_ret_picture_tag_bot, S5P_FIMV_D_RET_PICTURE_TAG_BOT_V8);
+ R(d_display_crop_info1, S5P_FIMV_D_DISPLAY_CROP_INFO1_V8);
+ R(d_display_crop_info2, S5P_FIMV_D_DISPLAY_CROP_INFO2_V8);
+ R(d_min_scratch_buffer_size, S5P_FIMV_D_MIN_SCRATCH_BUFFER_SIZE_V8);
+
+ /* encoder registers */
+ R(e_padding_ctrl, S5P_FIMV_E_PADDING_CTRL_V8);
+ R(e_rc_config, S5P_FIMV_E_RC_CONFIG_V8);
+ R(e_rc_mode, S5P_FIMV_E_RC_RPARAM_V8);
+ R(e_mv_hor_range, S5P_FIMV_E_MV_HOR_RANGE_V8);
+ R(e_mv_ver_range, S5P_FIMV_E_MV_VER_RANGE_V8);
+ R(e_rc_qp_bound, S5P_FIMV_E_RC_QP_BOUND_V8);
+ R(e_fixed_picture_qp, S5P_FIMV_E_FIXED_PICTURE_QP_V8);
+ R(e_vbv_buffer_size, S5P_FIMV_E_VBV_BUFFER_SIZE_V8);
+ R(e_vbv_init_delay, S5P_FIMV_E_VBV_INIT_DELAY_V8);
+ R(e_mb_rc_config, S5P_FIMV_E_MB_RC_CONFIG_V8);
+ R(e_aspect_ratio, S5P_FIMV_E_ASPECT_RATIO_V8);
+ R(e_extended_sar, S5P_FIMV_E_EXTENDED_SAR_V8);
+ R(e_h264_options, S5P_FIMV_E_H264_OPTIONS_V8);
+ R(e_min_scratch_buffer_size, S5P_FIMV_E_MIN_SCRATCH_BUFFER_SIZE_V8);
+
+ if (!IS_MFCV10(dev))
+ goto done;
+
+ /* Initialize registers used in MFC v10 only.
+ * Also, over-write the registers which have
+ * a different offset for MFC v10.
+ */
+
+ /* decoder registers */
+ R(d_static_buffer_addr, S5P_FIMV_D_STATIC_BUFFER_ADDR_V10);
+ R(d_static_buffer_size, S5P_FIMV_D_STATIC_BUFFER_SIZE_V10);
+
+ /* encoder registers */
+ R(e_num_t_layer, S5P_FIMV_E_NUM_T_LAYER_V10);
+ R(e_hier_qp_layer0, S5P_FIMV_E_HIERARCHICAL_QP_LAYER0_V10);
+ R(e_hier_bit_rate_layer0, S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER0_V10);
+ R(e_hevc_options, S5P_FIMV_E_HEVC_OPTIONS_V10);
+ R(e_hevc_refresh_period, S5P_FIMV_E_HEVC_REFRESH_PERIOD_V10);
+ R(e_hevc_lf_beta_offset_div2, S5P_FIMV_E_HEVC_LF_BETA_OFFSET_DIV2_V10);
+ R(e_hevc_lf_tc_offset_div2, S5P_FIMV_E_HEVC_LF_TC_OFFSET_DIV2_V10);
+ R(e_hevc_nal_control, S5P_FIMV_E_HEVC_NAL_CONTROL_V10);
+
+done:
+ return &mfc_regs;
+#undef S5P_MFC_REG_ADDR
+#undef R
+}
+
+/* Initialize opr function pointers for MFC v6 */
+static struct s5p_mfc_hw_ops s5p_mfc_ops_v6 = {
+ .alloc_dec_temp_buffers = s5p_mfc_alloc_dec_temp_buffers_v6,
+ .release_dec_desc_buffer = s5p_mfc_release_dec_desc_buffer_v6,
+ .alloc_codec_buffers = s5p_mfc_alloc_codec_buffers_v6,
+ .release_codec_buffers = s5p_mfc_release_codec_buffers_v6,
+ .alloc_instance_buffer = s5p_mfc_alloc_instance_buffer_v6,
+ .release_instance_buffer = s5p_mfc_release_instance_buffer_v6,
+ .alloc_dev_context_buffer =
+ s5p_mfc_alloc_dev_context_buffer_v6,
+ .release_dev_context_buffer =
+ s5p_mfc_release_dev_context_buffer_v6,
+ .dec_calc_dpb_size = s5p_mfc_dec_calc_dpb_size_v6,
+ .enc_calc_src_size = s5p_mfc_enc_calc_src_size_v6,
+ .set_enc_stream_buffer = s5p_mfc_set_enc_stream_buffer_v6,
+ .set_enc_frame_buffer = s5p_mfc_set_enc_frame_buffer_v6,
+ .get_enc_frame_buffer = s5p_mfc_get_enc_frame_buffer_v6,
+ .try_run = s5p_mfc_try_run_v6,
+ .clear_int_flags = s5p_mfc_clear_int_flags_v6,
+ .get_dspl_y_adr = s5p_mfc_get_dspl_y_adr_v6,
+ .get_dec_y_adr = s5p_mfc_get_dec_y_adr_v6,
+ .get_dspl_status = s5p_mfc_get_dspl_status_v6,
+ .get_dec_status = s5p_mfc_get_dec_status_v6,
+ .get_dec_frame_type = s5p_mfc_get_dec_frame_type_v6,
+ .get_disp_frame_type = s5p_mfc_get_disp_frame_type_v6,
+ .get_consumed_stream = s5p_mfc_get_consumed_stream_v6,
+ .get_int_reason = s5p_mfc_get_int_reason_v6,
+ .get_int_err = s5p_mfc_get_int_err_v6,
+ .err_dec = s5p_mfc_err_dec_v6,
+ .get_img_width = s5p_mfc_get_img_width_v6,
+ .get_img_height = s5p_mfc_get_img_height_v6,
+ .get_dpb_count = s5p_mfc_get_dpb_count_v6,
+ .get_mv_count = s5p_mfc_get_mv_count_v6,
+ .get_inst_no = s5p_mfc_get_inst_no_v6,
+ .get_enc_strm_size = s5p_mfc_get_enc_strm_size_v6,
+ .get_enc_slice_type = s5p_mfc_get_enc_slice_type_v6,
+ .get_enc_dpb_count = s5p_mfc_get_enc_dpb_count_v6,
+ .get_pic_type_top = s5p_mfc_get_pic_type_top_v6,
+ .get_pic_type_bot = s5p_mfc_get_pic_type_bot_v6,
+ .get_crop_info_h = s5p_mfc_get_crop_info_h_v6,
+ .get_crop_info_v = s5p_mfc_get_crop_info_v_v6,
+ .get_min_scratch_buf_size = s5p_mfc_get_min_scratch_buf_size,
+ .get_e_min_scratch_buf_size = s5p_mfc_get_e_min_scratch_buf_size,
+};
+
+struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v6(void)
+{
+ return &s5p_mfc_ops_v6;
+}
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h
new file mode 100644
index 0000000000..e4dd03c545
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h
+ *
+ * Header file for Samsung MFC (Multi Function Codec - FIMV) driver
+ * Contains declarations of hw related functions.
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_OPR_V6_H_
+#define S5P_MFC_OPR_V6_H_
+
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_opr.h"
+
+#define MFC_CTRL_MODE_CUSTOM MFC_CTRL_MODE_SFR
+
+#define MB_WIDTH(x_size) DIV_ROUND_UP(x_size, 16)
+#define MB_HEIGHT(y_size) DIV_ROUND_UP(y_size, 16)
+#define S5P_MFC_DEC_MV_SIZE_V6(x, y) (MB_WIDTH(x) * \
+ (((MB_HEIGHT(y)+1)/2)*2) * 64 + 128)
+#define S5P_MFC_DEC_MV_SIZE_V10(x, y) (MB_WIDTH(x) * \
+ (((MB_HEIGHT(y)+1)/2)*2) * 64 + 512)
+#define S5P_MFC_LCU_WIDTH(x_size) DIV_ROUND_UP(x_size, 32)
+#define S5P_MFC_LCU_HEIGHT(y_size) DIV_ROUND_UP(y_size, 32)
+
+#define s5p_mfc_dec_hevc_mv_size(x, y) \
+ (DIV_ROUND_UP(x, 64) * DIV_ROUND_UP(y, 64) * 256 + 512)
+
+/* Definition */
+#define ENC_MULTI_SLICE_MB_MAX ((1 << 30) - 1)
+#define ENC_MULTI_SLICE_BIT_MIN 2800
+#define ENC_INTRA_REFRESH_MB_MAX ((1 << 18) - 1)
+#define ENC_VBV_BUF_SIZE_MAX ((1 << 30) - 1)
+#define ENC_H264_LOOP_FILTER_AB_MIN -12
+#define ENC_H264_LOOP_FILTER_AB_MAX 12
+#define ENC_H264_RC_FRAME_RATE_MAX ((1 << 16) - 1)
+#define ENC_H263_RC_FRAME_RATE_MAX ((1 << 16) - 1)
+#define ENC_H264_PROFILE_MAX 3
+#define ENC_H264_LEVEL_MAX 42
+#define ENC_MPEG4_VOP_TIME_RES_MAX ((1 << 16) - 1)
+#define FRAME_DELTA_H264_H263 1
+#define TIGHT_CBR_MAX 10
+#define ENC_HEVC_RC_FRAME_RATE_MAX ((1 << 16) - 1)
+#define ENC_HEVC_QP_INDEX_MIN -12
+#define ENC_HEVC_QP_INDEX_MAX 12
+#define ENC_HEVC_LOOP_FILTER_MIN -12
+#define ENC_HEVC_LOOP_FILTER_MAX 12
+#define ENC_HEVC_LEVEL_MAX 62
+
+#define FRAME_DELTA_DEFAULT 1
+
+struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v6(void);
+const struct s5p_mfc_regs *s5p_mfc_init_regs_v6_plus(struct s5p_mfc_dev *dev);
+#endif /* S5P_MFC_OPR_V6_H_ */
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c
new file mode 100644
index 0000000000..187849841a
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include "s5p_mfc_common.h"
+#include "s5p_mfc_debug.h"
+#include "s5p_mfc_pm.h"
+
+static struct s5p_mfc_pm *pm;
+static struct s5p_mfc_dev *p_dev;
+static atomic_t clk_ref;
+
+int s5p_mfc_init_pm(struct s5p_mfc_dev *dev)
+{
+ int i;
+
+ pm = &dev->pm;
+ p_dev = dev;
+
+ pm->num_clocks = dev->variant->num_clocks;
+ pm->clk_names = dev->variant->clk_names;
+ pm->device = &dev->plat_dev->dev;
+ pm->clock_gate = NULL;
+
+ /* clock control */
+ for (i = 0; i < pm->num_clocks; i++) {
+ pm->clocks[i] = devm_clk_get(pm->device, pm->clk_names[i]);
+ if (IS_ERR(pm->clocks[i])) {
+ /* additional clocks are optional */
+ if (i && PTR_ERR(pm->clocks[i]) == -ENOENT) {
+ pm->clocks[i] = NULL;
+ continue;
+ }
+ mfc_err("Failed to get clock: %s\n",
+ pm->clk_names[i]);
+ return PTR_ERR(pm->clocks[i]);
+ }
+ }
+
+ if (dev->variant->use_clock_gating)
+ pm->clock_gate = pm->clocks[0];
+
+ pm_runtime_enable(pm->device);
+ atomic_set(&clk_ref, 0);
+ return 0;
+}
+
+void s5p_mfc_final_pm(struct s5p_mfc_dev *dev)
+{
+ pm_runtime_disable(pm->device);
+}
+
+int s5p_mfc_clock_on(void)
+{
+ atomic_inc(&clk_ref);
+ mfc_debug(3, "+ %d\n", atomic_read(&clk_ref));
+
+ return clk_enable(pm->clock_gate);
+}
+
+void s5p_mfc_clock_off(void)
+{
+ atomic_dec(&clk_ref);
+ mfc_debug(3, "- %d\n", atomic_read(&clk_ref));
+
+ clk_disable(pm->clock_gate);
+}
+
+int s5p_mfc_power_on(void)
+{
+ int i, ret = 0;
+
+ ret = pm_runtime_resume_and_get(pm->device);
+ if (ret < 0)
+ return ret;
+
+ /* clock control */
+ for (i = 0; i < pm->num_clocks; i++) {
+ ret = clk_prepare_enable(pm->clocks[i]);
+ if (ret < 0) {
+ mfc_err("clock prepare failed for clock: %s\n",
+ pm->clk_names[i]);
+ goto err;
+ }
+ }
+
+ /* prepare for software clock gating */
+ clk_disable(pm->clock_gate);
+
+ return 0;
+err:
+ while (--i >= 0)
+ clk_disable_unprepare(pm->clocks[i]);
+ pm_runtime_put(pm->device);
+ return ret;
+}
+
+int s5p_mfc_power_off(void)
+{
+ int i;
+
+ /* finish software clock gating */
+ clk_enable(pm->clock_gate);
+
+ for (i = 0; i < pm->num_clocks; i++)
+ clk_disable_unprepare(pm->clocks[i]);
+
+ return pm_runtime_put_sync(pm->device);
+}
+
diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h
new file mode 100644
index 0000000000..4159d2364e
--- /dev/null
+++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h
+ *
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ */
+
+#ifndef S5P_MFC_PM_H_
+#define S5P_MFC_PM_H_
+
+int s5p_mfc_init_pm(struct s5p_mfc_dev *dev);
+void s5p_mfc_final_pm(struct s5p_mfc_dev *dev);
+
+int s5p_mfc_clock_on(void);
+void s5p_mfc_clock_off(void);
+int s5p_mfc_power_on(void);
+int s5p_mfc_power_off(void);
+
+#endif /* S5P_MFC_PM_H_ */