Linux Media Controller development
 help / color / mirror / Atom feed
From: Jack Flusche <jackflusche@gmail.com>
To: linux-media@vger.kernel.org
Cc: Jack Flusche <jackflusche@gmail.com>,
	patrik.r.jakobsson@gmail.com, sakari.ailus@linux.intel.com,
	hverkuil@kernel.org
Subject: [RESEND PATCH 1/5] facetimehd: copy code from patjak/facetimehd
Date: Thu, 20 Aug 2026 11:21:50 -0700	[thread overview]
Message-ID: <20260820182156.18078-2-jackflusche@gmail.com> (raw)
In-Reply-To: <20260820182156.18078-1-jackflusche@gmail.com>

Copy over code from https//github.com/patjak/facetimehd

Signed-off-by: Jack Flusche <jackflusche@gmail.com>
---
 drivers/media/pci/facetimehd/fthd_buffer.c  |  125 ++
 drivers/media/pci/facetimehd/fthd_buffer.h  |   73 +
 drivers/media/pci/facetimehd/fthd_ddr.c     |  675 +++++++++
 drivers/media/pci/facetimehd/fthd_ddr.h     |   30 +
 drivers/media/pci/facetimehd/fthd_debugfs.c |  198 +++
 drivers/media/pci/facetimehd/fthd_debugfs.h |   17 +
 drivers/media/pci/facetimehd/fthd_drv.c     |  564 +++++++
 drivers/media/pci/facetimehd/fthd_drv.h     |  128 ++
 drivers/media/pci/facetimehd/fthd_hw.c      |  712 +++++++++
 drivers/media/pci/facetimehd/fthd_hw.h      |  128 ++
 drivers/media/pci/facetimehd/fthd_isp.c     | 1459 +++++++++++++++++++
 drivers/media/pci/facetimehd/fthd_isp.h     |  767 ++++++++++
 drivers/media/pci/facetimehd/fthd_reg.h     |  184 +++
 drivers/media/pci/facetimehd/fthd_ringbuf.c |  135 ++
 drivers/media/pci/facetimehd/fthd_ringbuf.h |   44 +
 drivers/media/pci/facetimehd/fthd_v4l2.c    |  790 ++++++++++
 drivers/media/pci/facetimehd/fthd_v4l2.h    |   34 +
 17 files changed, 6063 insertions(+)
 create mode 100644 drivers/media/pci/facetimehd/fthd_buffer.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_buffer.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_ddr.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_ddr.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_debugfs.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_debugfs.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_drv.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_drv.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_hw.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_hw.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_isp.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_isp.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_reg.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_ringbuf.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_ringbuf.h
 create mode 100644 drivers/media/pci/facetimehd/fthd_v4l2.c
 create mode 100644 drivers/media/pci/facetimehd/fthd_v4l2.h

diff --git a/drivers/media/pci/facetimehd/fthd_buffer.c b/drivers/media/pci/facetimehd/fthd_buffer.c
new file mode 100644
index 000000000000..fae443c1f380
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_buffer.c
@@ -0,0 +1,125 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/printk.h>
+#include "fthd_drv.h"
+#include "fthd_isp.h"
+#include "fthd_hw.h"
+#include "fthd_buffer.h"
+
+#define GET_IOMMU_PAGES(_x) (((_x) + 4095)/4096)
+
+struct buf_ctx {
+	struct fthd_plane plane[4];
+	struct isp_mem_obj *isphdr;
+};
+
+static int iommu_allocator_init(struct fthd_private *dev_priv)
+{
+        dev_priv->iommu = kzalloc(sizeof(struct resource), GFP_KERNEL);
+	if (!dev_priv->iommu)
+	    return -ENOMEM;
+
+	dev_priv->iommu->start = 0;
+	dev_priv->iommu->end = 4095;
+	return 0;
+}
+
+struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct sg_table *sgtable)
+{
+	struct iommu_obj *obj;
+	struct resource *root = dev_priv->iommu;
+	struct scatterlist *sg;
+	int ret, i, pos;
+	int total_len = 0, dma_length;
+	dma_addr_t dma_addr;
+	
+	for(i = 0; i < sgtable->nents; i++)
+		total_len += sg_dma_len(sgtable->sgl + i);
+	
+	if (!total_len)
+		return NULL;
+
+	total_len += 4095;
+	total_len /= 4096;
+	
+	obj = kzalloc(sizeof(struct iommu_obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->base.name = "S2 IOMMU";
+	ret = allocate_resource(root, &obj->base, total_len, root->start, root->end,
+				1, NULL, NULL);
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to allocate resource (size: %d, start: %Ld, end: %Ld)\n",
+			total_len, root->start, root->end);
+		kfree(obj);
+		obj = NULL;
+		return NULL;
+	}
+
+	obj->offset = obj->base.start - root->start;
+	obj->size = total_len;
+
+	pos = 0x9000 + obj->offset * 4;
+	for(i = 0; i < sgtable->nents; i++) {
+		sg = sgtable->sgl + i;
+		WARN_ON(sg->offset);
+		dma_addr = sg_dma_address(sg);
+		WARN_ON(dma_addr & 0xfff);
+		dma_addr >>= 12;
+		
+		for(dma_length = 0; dma_length < sg_dma_len(sg); dma_length += 0x1000) {
+		  //			pr_debug("IOMMU %08x -> %08llx (dma length %d)\n", pos, dma_addr, dma_length);
+			FTHD_S2_REG_WRITE(dma_addr++, pos);
+			pos += 4;
+		}
+	}
+
+	pr_debug("allocated %d pages @ %p / offset %d\n", obj->size, obj, obj->offset);
+	return obj;
+}
+
+void iommu_free(struct fthd_private *dev_priv, struct iommu_obj *obj)
+{
+	int i;
+	pr_debug("freeing %p\n", obj);
+
+	if (!obj)
+		return;
+	
+ 	for (i = obj->offset; i < obj->offset + obj->size; i++)
+		FTHD_S2_REG_WRITE(0, 0x9000 + i * 4);
+
+	release_resource(&obj->base);
+	kfree(obj);
+	obj = NULL;
+}
+
+static void iommu_allocator_destroy(struct fthd_private *dev_priv)
+{
+	kfree(dev_priv->iommu);
+}
+
+int fthd_buffer_init(struct fthd_private *dev_priv)
+{
+	int i;
+	for(i = 0; i < 0x1000; i++)
+		FTHD_S2_REG_WRITE(0, 0x9000 + i * 4);
+
+	return iommu_allocator_init(dev_priv);
+}
+
+void fthd_buffer_exit(struct fthd_private *dev_priv)
+{
+	iommu_allocator_destroy(dev_priv);
+}
diff --git a/drivers/media/pci/facetimehd/fthd_buffer.h b/drivers/media/pci/facetimehd/fthd_buffer.h
new file mode 100644
index 000000000000..2e76ded90215
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_buffer.h
@@ -0,0 +1,73 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+#ifndef FTHD_BUFFER_H
+#define FTHD_BUFFER_H
+
+#include <linux/scatterlist.h>
+#include "fthd_buffer.h"
+
+enum fthd_buffer_state {
+	BUF_FREE,
+	BUF_ALLOC,
+	BUF_DRV_QUEUED,
+	BUF_HW_QUEUED,
+};
+
+struct dma_descriptor {
+	u32 addr0;
+	u32 addr1;
+	u32 addr2;
+	u32 field_c;
+	u32 field_10;
+	u32 field_14;
+	u32 count;
+	u32 pool;
+	u64 tag;
+} __attribute__((packed));
+
+struct dma_descriptor_list {
+	u32 field0;
+    	u32 count;
+	struct dma_descriptor desc[4];
+	char unknown[216];
+} __attribute__((packed));
+
+struct iommu_obj {
+	struct resource base;
+	int size;
+	int offset;
+};
+
+struct fthd_plane {
+	u8 *virt;
+	u64 phys;
+	dma_addr_t dma;
+	int len;
+	struct iommu_obj *iommu;
+};
+
+struct h2t_buf_ctx {
+	enum fthd_buffer_state state;
+	struct vb2_buffer *vb;
+	struct iommu_obj *plane[4];
+	struct isp_mem_obj *dma_desc_obj;
+	struct dma_descriptor_list dma_desc_list;
+	/* waitqueue for signaling buffer completion */
+	wait_queue_head_t wq;
+	int done;
+};
+
+extern int setup_buffers(struct fthd_private *dev_priv);
+extern int fthd_buffer_init(struct fthd_private *dev_priv);
+extern void fthd_buffer_exit(struct fthd_private *dev_priv);
+extern void fthd_buffer_return_handler(struct fthd_private *dev_priv, u32 offset, int size);
+extern void fthd_buffer_queued_handler(struct fthd_private *dev_priv, u32 offset);
+extern struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct sg_table *);
+extern void iommu_free(struct fthd_private *dev_priv, struct iommu_obj *obj);
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_ddr.c b/drivers/media/pci/facetimehd/fthd_ddr.c
new file mode 100644
index 000000000000..df21537680db
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_ddr.c
@@ -0,0 +1,675 @@
+/*
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation.
+ *
+ */
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0)
+#include <linux/prandom.h>
+#else
+#include <linux/random.h>
+#endif
+
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_ddr.h"
+
+int fthd_ddr_verify_mem(struct fthd_private *dev_priv, u32 base, int count)
+{
+	u32 i, val, val_read;
+	int failed_bits = 0;
+	struct rnd_state state;
+
+	prandom_seed_state(&state, 0x12345678);
+
+	for (i = 0; i < count; i++) {
+		val = prandom_u32_state(&state);
+		FTHD_S2_MEM_WRITE(val, i * 4 + MEM_VERIFY_BASE);
+	}
+
+	prandom_seed_state(&state, 0x12345678);
+
+	for (i = 0; i < count; i++) {
+		val = prandom_u32_state(&state);
+		val_read = FTHD_S2_MEM_READ(i * 4 + MEM_VERIFY_BASE);
+
+		failed_bits |= val ^ val_read;
+	}
+
+	return ((failed_bits & 0xffff) | ((failed_bits >> 16) & 0xffff));
+}
+
+static int fthd_ddr_calibrate_rd_data_dly_fifo(struct fthd_private *dev_priv)
+{
+	u32 fifo_status[2];
+	u32 rden_bytes[3];
+	u32 rden_wl_setting, rden_bl_setting, setting, tmp;
+	u32 byte0_pass, byte1_pass;
+	int passed;
+
+	rden_bytes[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE);
+	rden_bytes[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0);
+	rden_bytes[2] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1);
+
+	FTHD_S2_REG_WRITE(0x30000, S2_DDR40_RDEN_BYTE);
+	FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0);
+	FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE1);
+
+	passed = 0;
+	setting = 1;
+	byte0_pass = 0;
+	byte1_pass = 0;
+	rden_wl_setting = 0;
+	rden_bl_setting = 0;
+
+	while (passed == 0) {
+		FTHD_S2_REG_WRITE(setting & 0x7, S2_DDR40_WL_RD_DATA_DLY);
+
+		fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+
+		tmp = FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS);
+		fifo_status[0] = tmp & 0xf;
+		fifo_status[1] = (tmp & 0xf0) >> 4;
+
+		FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR);
+
+		if (fifo_status[0] == 0) {
+			if (byte0_pass == 0)
+				byte0_pass = setting;
+		}
+
+		if (fifo_status[1] == 0) {
+			if (byte1_pass == 0)
+				byte1_pass = setting;
+		}
+
+		if (rden_wl_setting < 57)
+			rden_wl_setting += 7;
+		else
+			rden_bl_setting += 7;
+
+		if (rden_bl_setting > 63) {
+			setting++;
+			rden_wl_setting = 0;
+			rden_bl_setting = 0;
+		}
+
+		if (byte0_pass != 0 && byte1_pass != 0)
+			passed = 1;
+
+		/* Seems we default to setting=7 if no pass is found */
+		if (setting > 7) {
+			passed = 1;
+			setting = 7;
+
+			if (byte0_pass == 0)
+				byte0_pass = setting;
+
+			if (byte1_pass == 0)
+				byte1_pass = setting;
+		}
+
+		/* Write new setting */
+		FTHD_S2_REG_WRITE(rden_wl_setting | 0x30000,
+				  S2_DDR40_RDEN_BYTE);
+		FTHD_S2_REG_WRITE(rden_bl_setting | 0x30100,
+				  S2_DDR40_RDEN_BYTE0);
+		FTHD_S2_REG_WRITE(rden_bl_setting | 0x30100,
+				  S2_DDR40_RDEN_BYTE1);
+	}
+
+	setting = byte0_pass;
+
+	if (byte1_pass > setting)
+		setting = byte1_pass;
+
+	setting++;
+
+	if (setting > 7)
+		setting = 7;
+
+	/* Restore settings */
+	FTHD_S2_REG_WRITE(rden_bytes[0], S2_DDR40_RDEN_BYTE);
+	FTHD_S2_REG_WRITE(rden_bytes[1], S2_DDR40_RDEN_BYTE0);
+	FTHD_S2_REG_WRITE(rden_bytes[2], S2_DDR40_RDEN_BYTE1);
+
+	if (setting < 7)
+		setting++;
+
+	FTHD_S2_REG_WRITE(setting, S2_DDR40_WL_RD_DATA_DLY);
+	dev_info(&dev_priv->pdev->dev, "RD_DATA_DLY: 0x%x\n", setting);
+
+	return 0;
+}
+
+static int fthd_ddr_calibrate_one_re_fifo(struct fthd_private *dev_priv,
+			u32 *rden_byte, u32 *rden_byte0, u32 *rden_byte1)
+{
+	u32 fifo_status[2];
+	u32 wl_start;
+	u32 bl_pass[2] = {0, 0};
+	u32 bl_start[2] = {0, 0};
+	u32 word_setting, byte_setting, passed, delta;
+	u32 tmp;
+
+	delta = ((FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS) & 0xffc) >> 2) / 4;
+
+	/* Start with word and byte setting at 0 */
+	FTHD_S2_REG_WRITE(0x30000, S2_DDR40_RDEN_BYTE);
+	FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0);
+	FTHD_S2_REG_WRITE(0x30100, S2_DDR40_RDEN_BYTE0);
+
+	fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+
+	FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR);
+
+	word_setting = 0;
+	byte_setting = 0;
+	passed = 0;
+
+	while (passed == 0) {
+		fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+
+		fifo_status[0] =
+			FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) & 0xf;
+		fifo_status[1] =
+			(FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) &
+			 0xf0) >> 4;
+
+		FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR);
+
+		if (fifo_status[0] == 0) {
+			if (bl_pass[0] == 0)
+				bl_pass[0] = 1;
+			else
+				passed = 1;
+		}
+
+		if (fifo_status[1] == 0) {
+			if (bl_pass[1] == 0)
+				bl_pass[1] = 1;
+			else
+				passed = 1;
+		}
+
+		/* Still not passed */
+		if (passed == 0) {
+			if (word_setting < 63) {
+				word_setting++;
+				FTHD_S2_REG_WRITE(0x30000 | (word_setting & 0x3f),
+						  S2_DDR40_RDEN_BYTE);
+			} else {
+				byte_setting++;
+				FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f),
+						  S2_DDR40_RDEN_BYTE0);
+				FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f),
+						  S2_DDR40_RDEN_BYTE1);
+
+				if (word_setting > 64) {
+					dev_err(&dev_priv->pdev->dev,
+						"RDEN byte timeout\n");
+					return -EIO;
+				}
+			}
+		}
+	}
+
+	wl_start = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE) & 0x3f;
+
+	if (bl_pass[0] == 1) {
+		bl_start[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0) & 0x3f;
+		passed = 0;
+
+		while (passed == 0) {
+			byte_setting++;
+			if (byte_setting > 64) {
+				dev_err(&dev_priv->pdev->dev,
+					"RDEN BYTE1 timeout\n");
+				return -EIO;
+			}
+
+			FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f),
+					  S2_DDR40_RDEN_BYTE1);
+
+			fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+
+			fifo_status[0] = FTHD_S2_REG_READ(
+					S2_DDR40_WL_READ_FIFO_STATUS) & 0xf;
+			fifo_status[1] =
+				(FTHD_S2_REG_READ(S2_DDR40_WL_READ_FIFO_STATUS) &
+				 0xf0) >> 4;
+			FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR);
+
+			if (fifo_status[1] == 0)
+				passed = 1;
+		}
+
+		bl_start[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1) & 0x3f;
+	}
+	if (bl_pass[1] == 1) {
+		bl_start[1] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE1) & 0x3f;
+		passed = 0;
+
+		while (passed == 0) {
+			byte_setting++;
+			if (byte_setting > 64) {
+				dev_err(&dev_priv->pdev->dev,
+					"RDEN BYTE0 timeout\n");
+				return -EIO;
+			}
+
+			FTHD_S2_REG_WRITE(0x30100 | (byte_setting & 0x3f),
+					  S2_DDR40_RDEN_BYTE0);
+
+			fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+
+			fifo_status[0] = FTHD_S2_REG_READ(
+					S2_DDR40_WL_READ_FIFO_STATUS) & 0xf;
+			fifo_status[1] = FTHD_S2_REG_READ(
+					S2_DDR40_WL_READ_FIFO_STATUS) & 0xf0;
+			FTHD_S2_REG_WRITE(1, S2_DDR40_WL_READ_FIFO_CLEAR);
+
+			if (fifo_status[0] == 0)
+				passed = 1;
+		}
+
+		bl_start[0] = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0) & 0x3f;
+	}
+
+	*rden_byte = wl_start + delta;
+
+	if (*rden_byte > 63) {
+		tmp = *rden_byte - 63;
+		*rden_byte = 63;
+		*rden_byte0 = bl_start[0] + tmp;
+		*rden_byte1 = bl_start[1] + tmp;
+	} else {
+		*rden_byte0 = bl_start[0];
+		*rden_byte1 = bl_start[1];
+	}
+
+	if (*rden_byte0 > 63) {
+		*rden_byte0 = 63;
+	}
+	if (*rden_byte1 > 63) {
+		*rden_byte1 = 63;
+	}
+
+	return 0;
+}
+
+static int fthd_ddr_calibrate_re_byte_fifo(struct fthd_private *dev_priv)
+{
+	u32 rden_byte = 0;
+	u32 rden_byte0 = 0;
+	u32 rden_byte1 = 0;
+	int ret;
+
+	ret = fthd_ddr_calibrate_one_re_fifo(dev_priv, &rden_byte, &rden_byte0, &rden_byte1);
+	if (ret)
+		return ret;
+
+	rden_byte = (rden_byte & 0x3f) | 0x30000;
+	FTHD_S2_REG_WRITE(rden_byte, S2_DDR40_RDEN_BYTE);
+
+	rden_byte0 = (rden_byte0 & 0x3f) | 0x30100;
+	FTHD_S2_REG_WRITE(rden_byte0, S2_DDR40_RDEN_BYTE0);
+
+	rden_byte1 = (rden_byte1 & 0x3f) | 0x30100;
+	FTHD_S2_REG_WRITE(rden_byte1, S2_DDR40_RDEN_BYTE1);
+
+	dev_info(&dev_priv->pdev->dev,
+		 "RE BYTE FIFO success: b0 = 0x%x, b1 = 0x%x, b = 0x%x\n",
+		 rden_byte0, rden_byte1, rden_byte);
+
+	return 0;
+}
+
+/* Set default/generic read data strobe */
+static int fthd_ddr_generic_shmoo_rd_dqs(struct fthd_private *dev_priv,
+					 u32 *fail_bits)
+{
+	u32 retries, setting, tmp, offset;
+	u32 bytes[S2_DDR40_NUM_BYTE_LANES];
+	int i, j, ret, fail;
+
+	/* Save the current byte lanes */
+	for (i = 0; i < S2_DDR40_NUM_BYTE_LANES; i++) {
+		tmp = FTHD_S2_REG_READ(S2_DDR40_RDEN_BYTE0 +
+				       (i * S2_DDR40_BYTE_LANE_SIZE));
+		bytes[i] = tmp & 0x3f;
+	}
+
+	/* Clear all byte lanes */
+	for (i = 0; i < S2_DDR40_NUM_BYTE_LANES; i++) {
+		for (j = 0; j < 8; j++) {
+			offset = S2_DDR40_2A38 + (i * 0xa0) + (j * 8);
+
+			FTHD_S2_REG_WRITE(0x30000, offset - 4);
+			FTHD_S2_REG_WRITE(0x30000, offset);
+		}
+	}
+
+	setting = (FTHD_S2_REG_READ(S2_DDR40_PHY_DQ_CALIB_STATUS) >> 20) & 0x3f;
+
+	retries = 1000;
+	fail = 0;
+
+	while (retries-- > 0 && !fail) {
+		ret = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+		fail_bits[0] = ret;
+
+		if (ret == 0xffff) {
+			fail = 1;
+			break;
+		}
+
+		setting++;
+		tmp = (setting & 0x3f) | 0x30100;
+
+		/* Byte 0 */
+		FTHD_S2_REG_WRITE(tmp, S2_DDR40_2A08);
+		FTHD_S2_REG_WRITE(tmp, S2_DDR40_2A0C);
+
+		/* Byte 1 */
+		FTHD_S2_REG_WRITE(tmp, S2_DDR40_2AA8);
+		FTHD_S2_REG_WRITE(tmp, S2_DDR40_2AAC);
+
+		if (setting > 62)
+			fail = 1;
+
+		offset = S2_DDR40_RDEN_BYTE0;
+
+		/* Write byte lane settings */
+		for (i = 0; i < 2; i++) {
+			bytes[i]++;
+
+			if (bytes[i] > 62)
+				fail = 1;
+
+			FTHD_S2_REG_WRITE((bytes[i] & 0x3f) | 0x30100, offset);
+
+			offset += 0xa0;
+		}
+	}
+
+	if (retries == 0) {
+		dev_err(&dev_priv->pdev->dev, "Generic shmoo RD DQS timeout\n");
+		ret = -EIO;
+	}
+
+	if (fail)
+		dev_info(&dev_priv->pdev->dev, "Generic RD DQS failed\n");
+	else
+		dev_info(&dev_priv->pdev->dev, "Generic RD DQS succeeded\n");
+
+	/* It always fails, so just pass success */
+	return 0;
+}
+
+static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv,
+				     u32 *fails, u32 *settings)
+{
+	s32 pass_len[16];
+	u32 pass_start[16]; // u32 var_b0[16];
+	u32 pass_end[16]; // u32 var_f0[16];
+	int fail_sum, i, j, bit;
+	s32 setting;
+	printk(KERN_CONT "\n");
+
+	for (bit = 0; bit < 16; bit++) {
+		pass_start[bit] = 64;
+		pass_end[bit] = 64;
+
+		printk(KERN_CONT "%.2d: ", bit);
+
+		/* Start looking for start of pass */
+		for (i = 0; i < 63; i++) {
+			fail_sum = 0;
+
+			/* We check ahead the 6 next fail bits */
+			for (j = 0; (j < 6) && ((i + j) < 64); j++)
+				fail_sum += fails[i + j] & (1 << bit);
+
+			if (fail_sum) {
+				printk(KERN_CONT ".");
+			} else {
+				printk(KERN_CONT "O");
+
+				pass_start[bit] = i;
+				break;
+			}
+		}
+
+		/* Start looking for end of pass */
+		for (; i < 63; i++) {
+			if (fails[i] & (1 << bit)) {
+				if (pass_end[bit] == 64)
+					pass_end[bit] = i;
+
+				printk(KERN_CONT ".");
+			} else {
+				printk(KERN_CONT "O");
+			}
+		}
+
+		/* Calculate pass length */
+		pass_len[bit] = pass_end[bit] - pass_start[bit];
+
+		/* Calculate new setting */
+		setting = (pass_len[bit] / 2) + pass_start[bit];
+		if (setting < 0)
+			setting = 0;
+		else if (setting > 63)
+			setting = 63;
+		settings[bit] = setting;
+
+		printk(KERN_CONT " : start=%d end=%d len=%d new=%d\n", pass_start[bit],
+		       pass_end[bit], pass_len[bit], settings[bit]);
+	}
+
+	// Some global stuff that I need to figure out
+
+	return 0;
+}
+
+static int fthd_ddr_wr_dqs_setting(struct fthd_private *dev_priv, int set_bits,
+				   u32 *fail_bits, u32 *settings)
+{
+	u32 bl, setting, byte, bit, offset, tmp, start, inc, reg;
+	int i;
+
+	for (setting = 0; setting < 64; setting++) {
+		for (byte = 0; byte < 2; byte++) {
+			for (bit = 0; bit < 8; bit++) {
+				offset = S2_DDR40_2A38 + (byte * 0xa0) +
+					 (bit * 8);
+				tmp = setting | 0x30000;
+
+				if (set_bits & 1)
+					FTHD_S2_REG_WRITE(tmp, offset - 4);
+
+				if (set_bits & 2)
+					FTHD_S2_REG_WRITE(tmp, offset);
+			}
+		}
+		fail_bits[setting] = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+	}
+
+	if (set_bits == 3) {
+		start = 0;
+		inc = 1;
+	} else if (set_bits == 2) {
+		start = 0;
+		inc = 2;
+	} else {
+		start = 1;
+		inc = 2;
+	}
+
+	fthd_ddr_calibrate_rd_dqs(dev_priv, fail_bits, settings);
+
+	offset = 0;
+
+	for (bl = 0; bl < 2; bl++) {
+		reg = S2_DDR40_2A34 + bl * 0xa0;
+
+		for (i = start; i < 16; i += inc) {
+
+			if (settings[offset] == 0 || settings[offset] >= 63) {
+				dev_err(&dev_priv->pdev->dev,
+					"Bad VDL. Step %d = 0x%x\n",
+					offset, settings[offset]);
+				return -EINVAL;
+			}
+
+			tmp = (settings[offset] & 0x3f) | 0x30000;
+			FTHD_S2_REG_WRITE(tmp, reg);
+			if (set_bits == 3) {
+				if (i & 1)
+					offset++;
+			} else {
+				offset++;
+			}
+
+			reg += inc;
+		}
+	}
+
+	return 0;
+}
+
+static int fthd_ddr_calibrate_create_result(struct fthd_private *dev_priv)
+{
+	return 0;
+}
+
+static int fthd_ddr_generic_shmoo_calibrate_rd_dqs(
+						struct fthd_private *dev_priv)
+{
+	u32 settings[64]; /* Don't know the real size yet */
+	u32 fails[64]; /* Number of fails on a setting */
+	int ret;
+
+	ret = fthd_ddr_generic_shmoo_rd_dqs(dev_priv, fails);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_wr_dqs_setting(dev_priv, 3, fails, settings);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_wr_dqs_setting(dev_priv, 1, fails, settings);
+	if (ret)
+		return ret;
+
+
+	ret = fthd_ddr_wr_dqs_setting(dev_priv, 2, fails, settings);
+	if (ret)
+		return ret;
+
+	/* NOP for now */
+	ret = fthd_ddr_calibrate_create_result(dev_priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int fthd_ddr_calibrate_wr_dq(struct fthd_private *dev_priv, u32 *fails,
+				    u32 *settings)
+{
+	return 0;
+}
+
+static int fthd_ddr_generic_shmoo_calibrate_wr_dq(struct fthd_private *dev_priv)
+{
+	u32 fails[64];
+	u32 settings[64]; /* Size is actually 16? */
+	u32 setting, offset;
+	int bit, bl, ret = 0;
+
+	/* shmoo_wr_dq */
+	for (setting = 0; setting < 64; setting++) {
+		for (bl = 0; bl < 2; bl++) {
+			offset = S2_DDR40_2A10 + (bl * S2_DDR40_BYTE_LANE_SIZE);
+			for (bit = 0; bit < 8; bit++) {
+				FTHD_S2_REG_WRITE(setting | 0x30000,
+						  offset + (bit * 4));
+			}
+		}
+
+		fails[setting] = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+	}
+
+	fails[63] = 0xffff; /* Last setting is always a fail */
+
+	fthd_ddr_calibrate_wr_dq(dev_priv, fails, settings);
+
+	return ret;
+}
+
+static int fthd_ddr_generic_shmoo_calibrate_wr_dm(struct fthd_private *dev_priv)
+{
+	return 0;
+}
+
+static int fthd_ddr_generic_shmoo_calibrate_addr(struct fthd_private *dev_priv)
+{
+	return 0;
+}
+
+int fthd_ddr_calibrate(struct fthd_private *dev_priv)
+{
+	u32 reg;
+	int ret;
+
+	FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL);
+	FTHD_S2_REG_WRITE(0x200, S2_DDR40_PHY_VDL_CTL);
+
+	while (1) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS);
+		if (reg & 0x1)
+			break;
+	}
+
+	ret = fthd_ddr_calibrate_rd_data_dly_fifo(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_calibrate_re_byte_fifo(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_generic_shmoo_calibrate_rd_dqs(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_generic_shmoo_calibrate_wr_dq(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_generic_shmoo_calibrate_wr_dm(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_ddr_generic_shmoo_calibrate_addr(dev_priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
diff --git a/drivers/media/pci/facetimehd/fthd_ddr.h b/drivers/media/pci/facetimehd/fthd_ddr.h
new file mode 100644
index 000000000000..162e7c343ab7
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_ddr.h
@@ -0,0 +1,30 @@
+/*
+ * Broadcom PCIe 1570 webcam driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation.
+ *
+ */
+
+#ifndef _FTHD_DDR_H
+#define _FTHD_DDR_H
+
+#define MEM_VERIFY_BASE		0x0 /* 0x1000000 */
+#define MEM_VERIFY_NUM		128
+#define MEM_VERIFY_NUM_FULL	(1 * 1024 * 1024)
+
+int fthd_ddr_calibrate(struct fthd_private *dev_priv);
+int fthd_ddr_verify_mem(struct fthd_private *dev_priv, u32 base, int count);
+
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_debugfs.c b/drivers/media/pci/facetimehd/fthd_debugfs.c
new file mode 100644
index 000000000000..50ab5a75f14c
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_debugfs.c
@@ -0,0 +1,198 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/debugfs.h>
+#include "fthd_drv.h"
+#include "fthd_debugfs.h"
+#include "fthd_isp.h"
+#include "fthd_ringbuf.h"
+#include "fthd_hw.h"
+
+static ssize_t fthd_store_debug(struct file *file, const char __user *user_buf,
+				size_t count, loff_t *ppos)
+
+{
+	struct fthd_isp_debug_cmd cmd;
+	struct fthd_private *dev_priv = file->private_data;
+	int ret, opcode;
+	char buf[64];
+	int len;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	if (!strcmp(buf, "ps"))
+		opcode = CISP_CMD_DEBUG_PS;
+	else if (!strcmp(buf, "banner"))
+		opcode = CISP_CMD_DEBUG_BANNER;
+	else if (!strcmp(buf, "get_root"))
+		opcode = CISP_CMD_DEBUG_GET_ROOT_HANDLE;
+	else if (!strcmp(buf, "heap"))
+		opcode = CISP_CMD_DEBUG_HEAP_STATISTICS;
+	else if (!strcmp(buf, "irq"))
+		opcode = CISP_CMD_DEBUG_IRQ_STATISTICS;
+	else if (!strcmp(buf, "semaphore"))
+		opcode = CISP_CMD_DEBUG_SHOW_SEMAPHORE_STATUS;
+	else if (!strcmp(buf, "wiring"))
+		opcode = CISP_CMD_DEBUG_SHOW_WIRING_OPERATIONS;
+	else if (sscanf(buf, "get_object_by_name %s", (char *)&cmd.arg) == 1)
+		opcode = CISP_CMD_DEBUG_GET_OBJECT_BY_NAME;
+	else if (sscanf(buf, "dump_object %x", &cmd.arg[0]) == 1)
+		opcode = CISP_CMD_DEBUG_DUMP_OBJECT;
+	else if (!strcmp(buf, "dump_objects"))
+		opcode = CISP_CMD_DEBUG_DUMP_ALL_OBJECTS;
+	else if (!strcmp(buf, "show_objects"))
+		opcode = CISP_CMD_DEBUG_SHOW_OBJECT_GRAPH;
+	else if (sscanf(buf, "get_debug_level %i", &cmd.arg[0]) == 1)
+		opcode = CISP_CMD_DEBUG_GET_DEBUG_LEVEL;
+	else if (sscanf(buf, "set_debug_level %x %i", &cmd.arg[0], &cmd.arg[1]) == 2)
+		opcode = CISP_CMD_DEBUG_SET_DEBUG_LEVEL;
+	else if (sscanf(buf, "set_debug_level_rec %x %i", &cmd.arg[0], &cmd.arg[1]) == 2)
+		opcode = CISP_CMD_DEBUG_SET_DEBUG_LEVEL_RECURSIVE;
+	else if (!strcmp(buf, "get_fsm_count"))
+		opcode = CISP_CMD_DEBUG_GET_FSM_COUNT;
+	else if (sscanf(buf, "get_fsm_by_name %s", (char *)&cmd.arg[0]) == 1)
+		opcode = CISP_CMD_DEBUG_GET_FSM_BY_NAME;
+	else if (sscanf(buf, "get_fsm_by_index %i", &cmd.arg[0]) == 1)
+		opcode = CISP_CMD_DEBUG_GET_FSM_BY_INDEX;
+	else if (sscanf(buf, "get_fsm_debug_level %x", &cmd.arg[0]) == 1)
+		opcode = CISP_CMD_DEBUG_GET_FSM_DEBUG_LEVEL;
+	else if (sscanf(buf, "set_fsm_debug_level %x", &cmd.arg[0]) == 2)
+		opcode = CISP_CMD_DEBUG_SET_FSM_DEBUG_LEVEL;
+
+	else if (sscanf(buf, "%i %i\n", &opcode, &cmd.arg[0]) != 2)
+		return -EINVAL;
+	cmd.show_errors = 1;
+
+	ret = fthd_isp_debug_cmd(dev_priv, opcode, &cmd, sizeof(cmd), NULL);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+
+static int seq_channel_read(struct seq_file *seq, struct fthd_private *dev_priv,
+			struct fw_channel *chan)
+{
+	int i;
+	char pos;
+	u32 entry;
+
+	spin_lock_irq(&chan->lock);
+	for( i = 0; i < chan->size; i++) {
+		if (chan->ringbuf.idx == i)
+			pos = '*';
+		else
+			pos = ' ';
+		entry = get_entry_addr(dev_priv, chan, i);
+		seq_printf(seq, "%c%3.3d: ADDRESS %08x REQUEST_SIZE %08x RESPONSE_SIZE %08x\n",
+			   pos, i,
+			   FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS),
+			   FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE),
+			   FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE));
+	}
+	spin_unlock_irq(&chan->lock);
+	return 0;
+}
+
+static int seq_channel_terminal_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_terminal);
+}
+
+static int seq_channel_sharedmalloc_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_shared_malloc);
+}
+
+static int seq_channel_io_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_io);
+}
+
+static int seq_channel_io_t2h_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_io_t2h);
+}
+
+static int seq_channel_buf_h2t_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_buf_h2t);
+}
+
+static int seq_channel_buf_t2h_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_buf_t2h);
+}
+
+static int seq_channel_debug_read(struct seq_file *seq, void *data)
+
+{
+	struct fthd_private *dev_priv = dev_get_drvdata(seq->private);
+	return seq_channel_read(seq, dev_priv, dev_priv->channel_debug);
+}
+
+static const struct file_operations fops_debug = {
+	.read = NULL,
+	.write = fthd_store_debug,
+	.open = simple_open,
+	.owner = THIS_MODULE,
+	.llseek = default_llseek,
+};
+
+int fthd_debugfs_init(struct fthd_private *dev_priv)
+{
+	struct dentry *d, *top;
+
+	top = debugfs_create_dir("facetimehd", NULL);
+	if (IS_ERR(top))
+		return PTR_ERR(top);
+
+	d = debugfs_create_dir(dev_name(&dev_priv->pdev->dev), top);
+	if (IS_ERR(d)) {
+		debugfs_remove_recursive(top);
+		return PTR_ERR(d);
+	}
+
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_terminal", d, seq_channel_terminal_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_sharedmalloc", d, seq_channel_sharedmalloc_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_io", d, seq_channel_io_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_io_t2h", d, seq_channel_io_t2h_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_buf_h2t", d, seq_channel_buf_h2t_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_buf_t2h", d, seq_channel_buf_t2h_read);
+	debugfs_create_devm_seqfile(&dev_priv->pdev->dev, "channel_debug", d, seq_channel_debug_read);
+	debugfs_create_file("debug", S_IRUSR | S_IWUSR, d, dev_priv, &fops_debug);
+	dev_priv->debugfs = top;
+	return 0;
+}
+
+void fthd_debugfs_exit(struct fthd_private *dev_priv)
+{
+	debugfs_remove_recursive(dev_priv->debugfs);
+}
diff --git a/drivers/media/pci/facetimehd/fthd_debugfs.h b/drivers/media/pci/facetimehd/fthd_debugfs.h
new file mode 100644
index 000000000000..d3f8b74239e4
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_debugfs.h
@@ -0,0 +1,17 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#ifndef _FTHD_SYSFS_H
+#define _FTHD_SYSFS_H
+
+struct fthd_private;
+
+int fthd_debugfs_init(struct fthd_private *priv);
+void fthd_debugfs_exit(struct fthd_private *priv);
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_drv.c b/drivers/media/pci/facetimehd/fthd_drv.c
new file mode 100644
index 000000000000..c8d715122e5f
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_drv.c
@@ -0,0 +1,564 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0)
+#include <linux/pci-aspm.h>
+#endif
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/videodev2.h>
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_isp.h"
+#include "fthd_ringbuf.h"
+#include "fthd_buffer.h"
+#include "fthd_v4l2.h"
+#include "fthd_debugfs.h"
+
+static int fthd_pci_reserve_mem(struct fthd_private *dev_priv)
+{
+	unsigned long start;
+	unsigned long len;
+	int ret;
+
+	/* Reserve resources */
+	ret = pci_request_region(dev_priv->pdev, FTHD_PCI_S2_IO, "S2 IO");
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev, "Failed to request S2 IO\n");
+		return ret;
+	}
+
+	ret = pci_request_region(dev_priv->pdev, FTHD_PCI_ISP_IO, "ISP IO");
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev, "Failed to request ISP IO\n");
+		pci_release_region(dev_priv->pdev, FTHD_PCI_S2_IO);
+		return ret;
+	}
+
+	ret = pci_request_region(dev_priv->pdev, FTHD_PCI_S2_MEM, "S2 MEM");
+	if (ret) {
+		pci_release_region(dev_priv->pdev, FTHD_PCI_ISP_IO);
+		pci_release_region(dev_priv->pdev, FTHD_PCI_S2_IO);
+		return ret;
+	}
+
+	/* S2 IO */
+	start = pci_resource_start(dev_priv->pdev, FTHD_PCI_S2_IO);
+	len = pci_resource_len(dev_priv->pdev, FTHD_PCI_S2_IO);
+	dev_priv->s2_io = ioremap(start, len);
+	dev_priv->s2_io_len = len;
+
+	/* S2 MEM */
+	start = pci_resource_start(dev_priv->pdev, FTHD_PCI_S2_MEM);
+	len = pci_resource_len(dev_priv->pdev, FTHD_PCI_S2_MEM);
+	dev_priv->s2_mem = ioremap(start, len);
+	dev_priv->s2_mem_len = len;
+
+	/* ISP IO */
+	start = pci_resource_start(dev_priv->pdev, FTHD_PCI_ISP_IO);
+	len = pci_resource_len(dev_priv->pdev, FTHD_PCI_ISP_IO);
+	dev_priv->isp_io = ioremap(start, len);
+	dev_priv->isp_io_len = len;
+
+	pr_debug("Allocated S2 regs (BAR %d). %u bytes at 0x%p\n",
+		 FTHD_PCI_S2_IO, dev_priv->s2_io_len, dev_priv->s2_io);
+
+	pr_debug("Allocated S2 mem (BAR %d). %u bytes at 0x%p\n",
+		 FTHD_PCI_S2_MEM, dev_priv->s2_mem_len, dev_priv->s2_mem);
+
+	pr_debug("Allocated ISP regs (BAR %d). %u bytes at 0x%p\n",
+		 FTHD_PCI_ISP_IO, dev_priv->isp_io_len, dev_priv->isp_io);
+
+	return 0;
+}
+
+static void sharedmalloc_handler(struct fthd_private *dev_priv,
+				 struct fw_channel *chan,
+				 u32 entry)
+{
+	u32 request_size, response_size, address;
+	struct isp_mem_obj *obj;
+	int ret;
+
+	request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE);
+	response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE);
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~ 3;
+
+	if (address) {
+		pr_debug("Firmware wants to free memory at %08x\n", address);
+		FTHD_S2_MEMCPY_FROMIO(&obj, address - 64, sizeof(obj));
+		isp_mem_destroy(obj);
+
+		ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL);
+		if (ret)
+			pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+	} else {
+		if (!request_size)
+			return;
+		obj = isp_mem_create(dev_priv, FTHD_MEM_SHAREDMALLOC, request_size + 64);
+		if (!obj)
+			return;
+
+		pr_debug("Firmware allocated %d bytes at %08lx (tag %c%c%c%c)\n", request_size, obj->offset,
+			 response_size >> 24,response_size >> 16,
+			 response_size >> 8, response_size);
+		FTHD_S2_MEMCPY_TOIO(obj->offset, &obj, sizeof(obj));
+		ret = fthd_channel_ringbuf_send(dev_priv, chan, obj->offset + 64, 0, 0, NULL);
+		if (ret)
+			pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+
+	}
+
+}
+
+
+static void terminal_handler(struct fthd_private *dev_priv,
+				 struct fw_channel *chan,
+				 u32 entry)
+{
+	u32 request_size, response_size, address;
+	char buf[512];
+
+	request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE);
+	response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE);
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~ 3;
+
+	if (!address || !request_size)
+		return;
+
+	if (request_size > 512)
+		request_size = 512;
+	FTHD_S2_MEMCPY_FROMIO(buf, address, request_size);
+	pr_info("FWMSG: %.*s", request_size, buf);
+}
+
+static void buf_t2h_handler(struct fthd_private *dev_priv,
+			    struct fw_channel *chan,
+			    u32 entry)
+{
+	u32 request_size, response_size, address;
+	int ret;
+	request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE);
+	response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE);
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS);
+
+	if (address & 1)
+		return;
+
+
+	fthd_buffer_return_handler(dev_priv, address & ~3, request_size);
+	ret = fthd_channel_ringbuf_send(dev_priv, chan, (response_size & 0x10000000) ? address : 0,
+					0, 0x80000000, NULL);
+	if (ret)
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+
+}
+
+static void io_t2h_handler(struct fthd_private *dev_priv,
+				 struct fw_channel *chan,
+				 u32 entry)
+{
+	int ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL);
+	if (ret)
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+
+}
+
+static void fthd_handle_irq(struct fthd_private *dev_priv, struct fw_channel *chan)
+{
+	u32 entry;
+	int ret;
+
+	if (chan == dev_priv->channel_io) {
+		pr_debug("IO channel ready\n");
+		wake_up_interruptible(&chan->wq);
+		return;
+	}
+
+	if (chan == dev_priv->channel_buf_h2t) {
+		pr_debug("H2T channel ready\n");
+		wake_up_interruptible(&chan->wq);
+		return;
+	}
+
+	if (chan == dev_priv->channel_debug) {
+		pr_debug("DEBUG channel ready\n");
+		wake_up_interruptible(&chan->wq);
+		return;
+	}
+
+	while((entry = fthd_channel_ringbuf_receive(dev_priv, chan)) != (u32)-1) {
+		pr_debug("channel %s: message available, address %08x\n", chan->name, FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS));
+		if (chan == dev_priv->channel_shared_malloc) {
+			sharedmalloc_handler(dev_priv, chan, entry);
+		} else if (chan == dev_priv->channel_terminal) {
+			terminal_handler(dev_priv, chan, entry);
+			ret = fthd_channel_ringbuf_send(dev_priv, chan, 0, 0, 0, NULL);
+			if (ret)
+				pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+		} else if (chan == dev_priv->channel_buf_t2h) {
+			buf_t2h_handler(dev_priv, chan, entry);
+		} else if (chan == dev_priv->channel_io_t2h) {
+			io_t2h_handler(dev_priv, chan, entry);
+		}
+	}
+}
+
+static void fthd_irq_uninstall(struct fthd_private *dev_priv)
+{
+	free_irq(dev_priv->pdev->irq, dev_priv);
+}
+
+static void fthd_irq_work(struct work_struct *work)
+{
+	struct fthd_private *dev_priv = container_of(work, struct fthd_private, irq_work);
+	struct fw_channel *chan;
+
+	u32 pending;
+	int i = 0;
+
+	while(i++ < 500) {
+		spin_lock_irq(&dev_priv->io_lock);
+		pending = FTHD_ISP_REG_READ(ISP_IRQ_STATUS);
+		spin_unlock_irq(&dev_priv->io_lock);
+
+		if (!(pending & 0xf0))
+			break;
+
+		pci_write_config_dword(dev_priv->pdev, 0x94, 0);
+		spin_lock_irq(&dev_priv->io_lock);
+		FTHD_ISP_REG_WRITE(pending, ISP_IRQ_CLEAR);
+		spin_unlock_irq(&dev_priv->io_lock);
+		pci_write_config_dword(dev_priv->pdev, 0x90, 0x200);
+
+		for(i = 0; i < dev_priv->num_channels; i++) {
+			chan = dev_priv->channels[i];
+
+
+			BUG_ON(chan->source > 3);
+			if (!((0x10 << chan->source) & pending))
+				continue;
+			fthd_handle_irq(dev_priv, chan);
+		}
+	}
+
+	if (i >= 500) {
+		dev_err(&dev_priv->pdev->dev, "irq stuck, disabling\n");
+		fthd_irq_uninstall(dev_priv);
+	}
+	pci_write_config_dword(dev_priv->pdev, 0x94, 0x200);
+}
+
+static irqreturn_t fthd_irq_handler(int irq, void *arg)
+{
+	struct fthd_private *dev_priv = arg;
+	u32 pending;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev_priv->io_lock, flags);
+	pending = FTHD_ISP_REG_READ(ISP_IRQ_STATUS);
+	spin_unlock_irqrestore(&dev_priv->io_lock, flags);
+
+	if (!(pending & 0xf0))
+		return IRQ_NONE;
+
+	schedule_work(&dev_priv->irq_work);
+
+	return IRQ_HANDLED;
+}
+
+static int fthd_irq_install(struct fthd_private *dev_priv)
+{
+	int ret;
+
+	ret = request_irq(dev_priv->pdev->irq, fthd_irq_handler, IRQF_SHARED,
+			  KBUILD_MODNAME, (void *)dev_priv);
+
+	if (ret)
+		dev_err(&dev_priv->pdev->dev, "Failed to request IRQ\n");
+
+	return ret;
+}
+
+static int fthd_pci_set_dma_mask(struct fthd_private *dev_priv,
+				 unsigned int mask)
+{
+	int ret;
+
+	ret = dma_set_mask_and_coherent(&dev_priv->pdev->dev, DMA_BIT_MASK(mask));
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev, "Failed to set %u pci dma mask\n",
+			mask);
+		return ret;
+	}
+
+	dev_priv->dma_mask = mask;
+
+	return 0;
+}
+
+static void fthd_stop_firmware(struct fthd_private *dev_priv)
+{
+		fthd_isp_cmd_stop(dev_priv);
+	isp_powerdown(dev_priv);
+}
+
+static void fthd_pci_remove(struct pci_dev *pdev)
+{
+	struct fthd_private *dev_priv;
+
+	dev_priv = pci_get_drvdata(pdev);
+	if (!dev_priv)
+		goto out;
+
+	fthd_debugfs_exit(dev_priv);
+
+	fthd_v4l2_unregister(dev_priv);
+
+	fthd_stop_firmware(dev_priv);
+
+	fthd_irq_uninstall(dev_priv);
+
+	cancel_work_sync(&dev_priv->irq_work);
+
+	isp_uninit(dev_priv);
+
+	fthd_hw_deinit(dev_priv);
+
+	fthd_buffer_exit(dev_priv);
+
+	pci_disable_msi(pdev);
+
+	if (dev_priv->s2_io)
+		iounmap(dev_priv->s2_io);
+	if (dev_priv->s2_mem)
+		iounmap(dev_priv->s2_mem);
+	if (dev_priv->isp_io)
+		iounmap(dev_priv->isp_io);
+
+	pci_release_region(pdev, FTHD_PCI_S2_IO);
+	pci_release_region(pdev, FTHD_PCI_S2_MEM);
+	pci_release_region(pdev, FTHD_PCI_ISP_IO);
+out:
+	pci_disable_device(pdev);
+}
+
+static int fthd_pci_init(struct fthd_private *dev_priv)
+{
+	struct pci_dev *pdev = dev_priv->pdev;
+	int ret;
+
+
+	ret = pci_enable_device(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to enable device\n");
+		return ret;
+	}
+
+	/* ASPM must be disabled on the device or it hangs while streaming */
+	pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 |
+			       PCIE_LINK_STATE_CLKPM);
+
+	ret = fthd_pci_reserve_mem(dev_priv);
+	if (ret)
+		goto fail_enable;
+
+	ret = pci_enable_msi(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to enable MSI\n");
+		goto fail_reserve;
+	}
+
+	ret = fthd_irq_install(dev_priv);
+	if (ret)
+		goto fail_msi;
+
+	ret = fthd_pci_set_dma_mask(dev_priv, 64);
+	if (ret)
+		ret = fthd_pci_set_dma_mask(dev_priv, 32);
+
+	if (ret)
+		goto fail_irq;
+
+	dev_info(&pdev->dev, "Setting %ubit DMA mask\n", dev_priv->dma_mask);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,18,0)
+	pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(dev_priv->dma_mask));
+#else
+	dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(dev_priv->dma_mask));
+#endif
+
+	pci_set_master(pdev);
+	pci_set_drvdata(pdev, dev_priv);
+	return 0;
+
+fail_irq:
+	fthd_irq_uninstall(dev_priv);
+fail_msi:
+	pci_disable_msi(pdev);
+fail_reserve:
+	pci_release_region(pdev, FTHD_PCI_S2_IO);
+	pci_release_region(pdev, FTHD_PCI_S2_MEM);
+	pci_release_region(pdev, FTHD_PCI_ISP_IO);
+fail_enable:
+	pci_disable_device(pdev);
+	return ret;
+}
+
+static int fthd_firmware_start(struct fthd_private *dev_priv)
+{
+	int ret;
+
+	ret = fthd_isp_cmd_start(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_print_enable(dev_priv, 1);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_camera_config(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_info(dev_priv);
+	if (ret)
+		return ret;
+
+	/* Query the sensor's native resolution now so fthd_v4l2_register()
+	 * can advertise it. Non-fatal: if it fails the V4L2 layer falls back
+	 * to a default size. */
+	fthd_isp_cmd_channel_camera_config(dev_priv);
+
+	return fthd_isp_cmd_set_loadfile(dev_priv);
+
+}
+
+static int fthd_pci_probe(struct pci_dev *pdev,
+			  const struct pci_device_id *entry)
+{
+	struct fthd_private *dev_priv;
+	int ret;
+
+	dev_info(&pdev->dev, "Found FaceTime HD camera with device id: %x\n",
+		 pdev->device);
+
+	dev_priv = kzalloc(sizeof(struct fthd_private), GFP_KERNEL);
+	if (!dev_priv) {
+		dev_err(&pdev->dev, "Failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	dev_priv->ddr_model = 4;
+	dev_priv->ddr_speed = 450;
+	dev_priv->frametime = 40; /* 25 fps */
+
+	spin_lock_init(&dev_priv->io_lock);
+	mutex_init(&dev_priv->vb2_queue_lock);
+
+	mutex_init(&dev_priv->ioctl_lock);
+	INIT_LIST_HEAD(&dev_priv->buffer_queue);
+	INIT_WORK(&dev_priv->irq_work, fthd_irq_work);
+
+	dev_priv->pdev = pdev;
+
+	ret = fthd_pci_init(dev_priv);
+	if (ret)
+		goto fail_work;
+
+	ret = fthd_buffer_init(dev_priv);
+	if (ret)
+		goto fail_pci;
+
+	ret = fthd_hw_init(dev_priv);
+	if (ret)
+		goto fail_buffer;
+
+	ret = fthd_firmware_start(dev_priv);
+	if (ret)
+		goto fail_hw;
+
+	ret = fthd_v4l2_register(dev_priv);
+	if (ret)
+		goto fail_firmware;
+
+	ret = fthd_debugfs_init(dev_priv);
+	if (ret)
+		goto fail_v4l2;
+	return 0;
+fail_v4l2:
+	fthd_v4l2_unregister(dev_priv);
+fail_firmware:
+	fthd_stop_firmware(dev_priv);
+fail_hw:
+	fthd_hw_deinit(dev_priv);
+fail_buffer:
+	fthd_buffer_exit(dev_priv);
+fail_pci:
+	fthd_irq_uninstall(dev_priv);
+	pci_disable_msi(pdev);
+	pci_release_region(pdev, FTHD_PCI_S2_IO);
+	pci_release_region(pdev, FTHD_PCI_S2_MEM);
+	pci_release_region(pdev, FTHD_PCI_ISP_IO);
+	pci_disable_device(pdev);
+
+fail_work:
+	cancel_work_sync(&dev_priv->irq_work);
+	kfree(dev_priv);
+	return ret;
+}
+
+#ifdef CONFIG_PM
+static int fthd_pci_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	fthd_pci_remove(pdev);
+
+	return 0;
+}
+
+static int fthd_pci_resume(struct pci_dev *pdev)
+{
+	fthd_pci_probe(pdev, NULL);
+
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct pci_device_id fthd_pci_id_table[] = {
+	{ PCI_DEVICE(0x14e4, 0x1570), 4 },
+	{ 0, },
+};
+
+static struct pci_driver fthd_pci_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = fthd_pci_probe,
+	.remove = fthd_pci_remove,
+	.shutdown = fthd_pci_remove,
+	.id_table = fthd_pci_id_table,
+#ifdef CONFIG_PM
+	.suspend = fthd_pci_suspend,
+	.resume = fthd_pci_resume,
+#endif
+};
+
+module_pci_driver(fthd_pci_driver);
+
+MODULE_FIRMWARE("facetimehd/firmware.bin");
+MODULE_DEVICE_TABLE(pci, fthd_pci_id_table);
+MODULE_AUTHOR("Patrik Jakobsson <patrik.r.jakobsson@gmail.com>");
+MODULE_DESCRIPTION("FacetimeHD camera driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/pci/facetimehd/fthd_drv.h b/drivers/media/pci/facetimehd/fthd_drv.h
new file mode 100644
index 000000000000..439143fd337d
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_drv.h
@@ -0,0 +1,128 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#ifndef _FTHD_DRV_H
+#define _FTHD_DRV_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/version.h>
+#include <media/videobuf2-dma-sg.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include "fthd_reg.h"
+#include "fthd_ringbuf.h"
+#include "fthd_buffer.h"
+#include "fthd_v4l2.h"
+
+#define FTHD_PCI_S2_IO  0
+#define FTHD_PCI_S2_MEM 2
+#define FTHD_PCI_ISP_IO 4
+
+#define FTHD_BUFFERS 4
+
+enum FW_CHAN_TYPE {
+	FW_CHAN_TYPE_OUT=0,
+	FW_CHAN_TYPE_IN=1,
+	FW_CHAN_TYPE_UNI_IN=2,
+};
+
+struct fw_channel {
+	u32 offset;
+	u32 size;
+	u32 source;
+	u32 type;
+	struct fthd_ringbuf ringbuf;
+	spinlock_t lock;
+	/* waitqueue for signaling completion */
+	wait_queue_head_t wq;
+	char *name;
+};
+
+struct fthd_private {
+	struct pci_dev *pdev;
+	unsigned int dma_mask;
+
+	struct v4l2_device v4l2_dev;
+	struct video_device *videodev;
+	struct mutex ioctl_lock;
+	int users;
+	/* lock for synchronizing with irq/workqueue */
+	spinlock_t io_lock;
+
+	/* Mapped PCI resources */
+	void __iomem *s2_io;
+	u32 s2_io_len;
+
+	void __iomem *s2_mem;
+	u32 s2_mem_len;
+
+	void __iomem *isp_io;
+	u32 isp_io_len;
+
+	struct work_struct irq_work;
+
+	/* Hardware info */
+	u32 core_clk;
+	u32 ddr_model;
+	u32 ddr_speed;
+	u32 vdl_step_size;
+
+	u32 ddr_phy_regs[DDR_PHY_NUM_REG];
+
+	/* Root resource for memory management */
+	struct resource *mem;
+	/* Resource for managing IO mmu slots */
+	struct resource *iommu;
+	/* ISP memory objects */
+	struct isp_mem_obj *firmware;
+	struct isp_mem_obj *set_file;
+	struct isp_mem_obj *ipc_queue;
+	struct isp_mem_obj *heap;
+
+	/* Firmware channels */
+	int num_channels;
+	struct fw_channel **channels;
+	struct fw_channel *channel_terminal;
+	struct fw_channel *channel_io;
+	struct fw_channel *channel_debug;
+	struct fw_channel *channel_buf_h2t;
+	struct fw_channel *channel_buf_t2h;
+	struct fw_channel *channel_shared_malloc;
+	struct fw_channel *channel_io_t2h;
+
+	/* camera config */
+	int sensor_count;
+	int sensor_id0;
+	int sensor_id1;
+	/* Native sensor resolution, read from the firmware's per-channel camera
+	 * config. MacBookPro sensors report 1280x720; the 12-inch MacBook
+	 * (MacBook8,1, sensor 1675) reports 848x588. 0 until detected. */
+	unsigned int sensor_width;
+	unsigned int sensor_height;
+
+	struct fthd_fmt fmt;
+
+	struct vb2_queue vb2_queue;
+	struct mutex vb2_queue_lock;
+	struct list_head buffer_queue;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
+	struct vb2_alloc_ctx *alloc_ctx;
+#endif
+	struct h2t_buf_ctx h2t_bufs[FTHD_BUFFERS];
+
+	struct v4l2_ctrl_handler v4l2_ctrl_handler;
+	int frametime;
+	unsigned int sequence;
+	struct dentry *debugfs;
+};
+
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_hw.c b/drivers/media/pci/facetimehd/fthd_hw.c
new file mode 100644
index 000000000000..8180072922d4
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_hw.c
@@ -0,0 +1,712 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#include <linux/delay.h>
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_ddr.h"
+#include "fthd_isp.h"
+
+static int fthd_hw_s2_pll_reset(struct fthd_private *dev_priv)
+{
+	FTHD_S2_REG_WRITE(0x40, S2_PLL_CTRL_2C);
+	FTHD_S2_REG_WRITE(0x0, S2_PLL_CTRL_2C);
+	FTHD_S2_REG_WRITE(0xbcbc1500, S2_PLL_CTRL_100);
+	FTHD_S2_REG_WRITE(0x0, S2_PLL_CTRL_14);
+
+	udelay(10000);
+
+	FTHD_S2_REG_WRITE(0x3, S2_PLL_CTRL_14);
+
+	dev_info(&dev_priv->pdev->dev, "PLL reset finished\n");
+
+	return 0;
+}
+
+static int fthd_hw_s2_init_pcie_link(struct fthd_private *dev_priv)
+{
+	u32 reg;
+
+	reg = FTHD_S2_REG_READ(S2_PCIE_LINK_D000);
+	FTHD_S2_REG_WRITE(reg | 0x10, S2_PCIE_LINK_D000);
+
+	FTHD_S2_REG_WRITE(0x1804, S2_PCIE_LINK_D120);
+	FTHD_S2_REG_WRITE(0xac5800, S2_PCIE_LINK_D124);
+	FTHD_S2_REG_WRITE(0x1804, S2_PCIE_LINK_D120);
+
+	/* Check if PLL is powered down when S2 PCIe link is in L1 state */
+	reg = FTHD_S2_REG_READ(S2_PCIE_LINK_D124);
+	if (reg != 0xac5800) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to init S2 PCIe link: %08x\n", reg);
+		return -EIO;
+	}
+
+	/* PLL is powered down */
+	dev_info(&dev_priv->pdev->dev, "S2 PCIe link init succeeded\n");
+
+	FTHD_S2_REG_WRITE(0x1f08, S2_PCIE_LINK_D128);
+	FTHD_S2_REG_WRITE(0x80008610, S2_PCIE_LINK_D12C);
+	FTHD_S2_REG_WRITE(0x1608, S2_PCIE_LINK_D128);
+	FTHD_S2_REG_WRITE(0x8000fc00, S2_PCIE_LINK_D12C);
+	FTHD_S2_REG_WRITE(0x1f08, S2_PCIE_LINK_D128);
+	FTHD_S2_REG_WRITE(0x80008610, S2_PCIE_LINK_D12C);
+	FTHD_S2_REG_WRITE(0x1708, S2_PCIE_LINK_D128);
+	FTHD_S2_REG_WRITE(0x800005bf, S2_PCIE_LINK_D12C);
+
+	return 0;
+}
+
+static int fthd_hw_s2_pll_init(struct fthd_private *dev_priv, u32 ddr_speed)
+{
+	u32 ref_clk_25;
+	u32 reg;
+	int retries = 0;
+
+	reg = FTHD_S2_REG_READ(S2_PLL_REFCLK);
+	ref_clk_25 = reg & S2_PLL_REFCLK_25MHZ ? 1 : 0;
+
+	if (ref_clk_25)
+		dev_info(&dev_priv->pdev->dev, "Refclk: 25MHz (0x%x)\n", reg);
+	else
+		dev_info(&dev_priv->pdev->dev, "Refclk: 24MHz (0x%x\n", reg);
+
+	if (ddr_speed == 400) {
+		if (ref_clk_25) {
+			/* Ref clk 25 */
+			FTHD_S2_REG_WRITE(0x00400078, S2_PLL_CTRL_510);
+			FTHD_S2_REG_WRITE(0x19280804, S2_PLL_CTRL_24);
+		} else {
+			/* Ref clk 24 */
+			FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20);
+			FTHD_S2_REG_WRITE(0x14280603, S2_PLL_CTRL_24);
+		}
+	} else if (ddr_speed == 300) {
+		if (ref_clk_25) {
+			/* Ref clk 25 */
+			FTHD_S2_REG_WRITE(0x00480078, S2_PLL_CTRL_510);
+			FTHD_S2_REG_WRITE(0x19280c06, S2_PLL_CTRL_24);
+		} else {
+			/* Ref clk 24 */
+			FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20);
+			FTHD_S2_REG_WRITE(0x14280804, S2_PLL_CTRL_24);
+		}
+	} else if (ddr_speed == 200) {
+		if (ref_clk_25) {
+			/* Ref clk 25 */
+			FTHD_S2_REG_WRITE(0x00400078, S2_PLL_CTRL_510);
+			FTHD_S2_REG_WRITE(0x19281008, S2_PLL_CTRL_24);
+		} else {
+			/* Ref clk 24 */
+			FTHD_S2_REG_WRITE(0x03200000, S2_PLL_CTRL_20);
+			FTHD_S2_REG_WRITE(0x14280c06, S2_PLL_CTRL_24);
+		}
+	} else {
+		if (ddr_speed != 450) {
+			dev_err(&dev_priv->pdev->dev,
+				"Unsupported DDR speed %uMHz, using 450MHz\n",
+				ddr_speed);
+			ddr_speed = 450;
+		}
+
+		if (ref_clk_25) {
+			/* Ref clk 25 */
+			FTHD_S2_REG_WRITE(0x0048007d, S2_PLL_CTRL_510);
+			FTHD_S2_REG_WRITE(0x19280904, S2_PLL_CTRL_24);
+		} else {
+			/* Ref clk 24 */
+			FTHD_S2_REG_WRITE(0x04b00000, S2_PLL_CTRL_20);
+			FTHD_S2_REG_WRITE(0x14280904, S2_PLL_CTRL_24);
+
+		}
+	}
+
+	fthd_hw_s2_pll_reset(dev_priv);
+
+	dev_info(&dev_priv->pdev->dev, "Waiting for S2 PLL to lock at %d MHz\n",
+		 ddr_speed);
+
+	do {
+		reg = FTHD_S2_REG_READ(S2_PLL_CMU_STATUS);
+		udelay(10);
+		retries++;
+	} while (((reg & 0xff00) & S2_PLL_CMU_STATUS_LOCKED) && retries <= 10000);
+
+	if (retries > 10000) {
+		dev_info(&dev_priv->pdev->dev, "Failed to lock S2 PLL: 0x%x\n",
+			 reg);
+		return -EINVAL;
+	} else {
+		dev_info(&dev_priv->pdev->dev, "S2 PLL is locked after %d us\n",
+			 (retries * 10));
+	}
+
+	reg = FTHD_S2_REG_READ(S2_PLL_STATUS_A8);
+	FTHD_S2_REG_WRITE(reg | S2_PLL_BYPASS, S2_PLL_STATUS_A8);
+	udelay(10000);
+
+	reg = FTHD_S2_REG_READ(S2_PLL_STATUS_A8);
+	if (reg & S2_PLL_BYPASS)
+		dev_info(&dev_priv->pdev->dev, "S2 PLL is in bypass mode\n");
+	else
+		dev_info(&dev_priv->pdev->dev, "S2 PLL is in non-bypass mode\n");
+
+	return 0;
+}
+
+static int fthd_hw_s2_preinit_ddr_controller_soc(struct fthd_private *dev_priv)
+{
+	/* Wingardium leviosa */
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1100);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1104);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1108);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_110C);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1110);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1114);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_1118);
+	FTHD_S2_REG_WRITE(0x203, S2_DDR_REG_111C);
+
+	return 0;
+}
+
+static int fthd_hw_ddr_phy_soft_reset(struct fthd_private *dev_priv)
+{
+	/* Clear status bits? */
+	FTHD_S2_REG_WRITE(0x281, S2_PLL_STATUS_A8);
+
+	FTHD_S2_REG_WRITE(0xfffff, S2_PLL_CTRL_9C);
+
+	udelay(10000);
+
+	FTHD_S2_REG_WRITE(0xffbff, S2_PLL_CTRL_9C);
+
+	return 0;
+}
+
+static inline int fthd_hw_ddr_status_busy(struct fthd_private *dev_priv,
+					  int retries, int delay)
+{
+	int reg = 0, i;
+
+	for (i = 0; i < retries; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR_STATUS_2018);
+		if (!(reg & S2_DDR_STATUS_BUSY))
+			break;
+
+		if (delay > 0)
+			udelay(delay);
+	}
+
+	if (i >= retries) {
+		dev_err(&dev_priv->pdev->dev,
+			"S2_DDR_STATUS_2018 busy: retries=%d, udelay=%d, reg=0x%08x\n",
+			retries, delay, reg);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int fthd_hw_ddr_rewrite_mode_regs(struct fthd_private *dev_priv)
+{
+	int ret, val;
+
+	FTHD_S2_REG_WRITE(0x02000802, S2_DDR_2014);
+
+	ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5);
+	if (ret != 0)
+		return ret;
+
+	FTHD_S2_REG_WRITE(0x3, S2_DDR_2014);
+
+	ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5);
+	if (ret != 0)
+		return ret;
+
+	FTHD_S2_REG_WRITE(0x1, S2_DDR_2014);
+
+	ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5);
+	if (ret != 0)
+		return ret;
+
+	if (dev_priv->ddr_speed == 450)
+		val = 0x17003000;
+	else
+		val = 0x17002000;
+
+	FTHD_S2_REG_WRITE(val, S2_DDR_2014);
+
+	ret = fthd_hw_ddr_status_busy(dev_priv, 500, 5);
+	if (ret != 0)
+		return ret;
+
+	dev_info(&dev_priv->pdev->dev,
+		 "Rewrite DDR mode registers succeeded\n");
+
+	return 0;
+}
+
+static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv)
+{
+	u32 cmd;
+	u32 val;
+	u32 reg;
+	u32 step_size, vdl_fine, vdl_coarse;
+	u32 vtt_cons, vtt_ovr;
+	int ret, i;
+
+	/* Read PCI config command register */
+	ret = pci_read_config_dword(dev_priv->pdev, 4, &cmd);
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev, "Failed to read PCI config\n");
+		return -EIO;
+	}
+
+	if ((cmd & 0x07) == 0) {
+		dev_err(&dev_priv->pdev->dev,
+			"PCI link in illegal state, cfg_cmd_reg: 0x%x\n", cmd);
+		return -EIO;
+	}
+
+	reg = FTHD_S2_REG_READ(S2_PLL_CTRL_9C);
+	FTHD_S2_REG_WRITE(reg & 0xfffffcff, S2_PLL_CTRL_9C);
+	FTHD_S2_REG_WRITE(reg | 0x300, S2_PLL_CTRL_9C);
+
+	fthd_hw_s2_pll_init(dev_priv, dev_priv->ddr_speed);
+
+	fthd_hw_ddr_phy_soft_reset(dev_priv);
+
+	FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_DRV_PAD_CTL);
+	FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_CLK_PAD_DISABLE);
+
+	/* Disable the hardware frequency change function */
+	FTHD_S2_REG_WRITE(0x3f4, S2_20F8);
+
+	/* Setup the PLL */
+	FTHD_S2_REG_WRITE(0x40, S2_2434);
+	FTHD_S2_REG_WRITE(0x10000000, S2_2438);
+	FTHD_S2_REG_WRITE(0x4, S2_2424);
+	FTHD_S2_REG_WRITE(0x1f37291, S2_2430);
+
+	/* Wait for DDR PLL to lock */
+	for (i = 0; i <= 10000; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR_PLL_STATUS_2444);
+		if (reg & S2_DDR_PLL_STATUS_2444_LOCKED)
+			break;
+		udelay(10);
+	}
+
+	if (i > 10000) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to lock DDR PHY PLL in stage 1\n");
+		return -EIO;
+	}
+
+	FTHD_S2_REG_WRITE(0x1f37205, S2_2430);
+
+	for (i = 0; i <= 10000; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR_PLL_STATUS_241C);
+		if (reg & S2_DDR_PLL_STATUS_241C_LOCKED)
+			break;
+		udelay(10);
+	}
+
+	if (i > 10000) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to lock DDR PHY PLL in stage 2\n");
+		return -EIO;
+	}
+
+	udelay(10000);
+
+	/* WL */
+	FTHD_S2_REG_WRITE(0x0c10, S2_DDR40_PHY_PLL_DIV);
+	FTHD_S2_REG_WRITE(0x0010, S2_DDR40_PHY_PLL_CFG);
+
+	for (i = 0; i <= 10000; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_STATUS);
+		if (reg & S2_DDR40_PHY_PLL_STATUS_LOCKED)
+			break;
+		udelay(10);
+	}
+
+	if (i > 10000) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to lock DDR PHY PLL in stage 3\n");
+		return -EIO;
+	}
+
+	dev_info(&dev_priv->pdev->dev,
+		 "DDR40 PHY PLL locked on safe settings\n");
+
+	/* Default is DDR model 4 */
+	switch (dev_priv->ddr_model) {
+	case 4:
+		val = 0x46a00c2;
+		break;
+	case 2:
+		val = 0x42500c2;
+		break;
+	default:
+		val = 0;
+	}
+
+	FTHD_S2_REG_WRITE(0x10737545, S2_DDR_20A0);
+	FTHD_S2_REG_WRITE(0x12643173, S2_DDR_20A4);
+	FTHD_S2_REG_WRITE(0xff3f, S2_DDR_20A8);
+	FTHD_S2_REG_WRITE(val, S2_DDR_20B0);
+	FTHD_S2_REG_WRITE(0x101f, S2_DDR_2118);
+	FTHD_S2_REG_WRITE(0x1c0, S2_DDR40_PHY_AUX_CTL);
+
+	switch (dev_priv->ddr_model) {
+	case 4:
+		val = 0x2159518;
+		break;
+	case 2:
+		val = 0x2155558;
+		break;
+	}
+
+	FTHD_S2_REG_WRITE(val, S2_DDR40_STRAP_CTL);
+
+	if (dev_priv->ddr_speed == 450)
+		val = 0x108307;
+	else
+		val = 0x108286;
+
+	FTHD_S2_REG_WRITE(val, S2_DDR40_STRAP_CTL_2);
+
+	/* Strap control */
+	FTHD_S2_REG_WRITE(0x2159559, S2_DDR40_STRAP_CTL);
+
+	/* Polling for STRAP valid */
+	for (i = 0; i < 10000; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_STRAP_STATUS);
+		if (reg & 0x1)
+			break;
+		udelay(10);
+	}
+
+	if (i >= 10000) {
+		dev_err(&dev_priv->pdev->dev,
+			"Timeout waiting for STRAP valid\n");
+		return -ENODEV;
+	} else {
+		dev_info(&dev_priv->pdev->dev, "STRAP valid\n");
+	}
+
+	/* Manual DDR40 PHY init */
+	if (dev_priv->ddr_speed != 450) {
+		dev_warn(&dev_priv->pdev->dev,
+			 "DDR frequency is %u (should be 450 MHz)",
+			 dev_priv->ddr_speed);
+	}
+
+	dev_info(&dev_priv->pdev->dev,
+		 "Configuring DDR PLLs for %u MHz\n", dev_priv->ddr_speed);
+
+	if ((dev_priv->ddr_speed * 2) < 500)
+		val = 0x2040;
+	else
+		val = 0x810;
+
+	/* Start programming the DDR PLL */
+
+	reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_DIV);
+	reg &= 0xffffc700;
+	val |= reg;
+
+	FTHD_S2_REG_WRITE(val, S2_DDR40_PHY_PLL_DIV);
+
+	reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_CFG);
+	reg &= 0xfffffffd;
+	FTHD_S2_REG_WRITE(reg, S2_DDR40_PHY_PLL_CFG);
+
+	/* Start polling for the lock */
+	for (i = 0; i < 100; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_PHY_PLL_STATUS);
+		if (reg & S2_DDR40_PHY_PLL_STATUS_LOCKED)
+			break;
+		udelay(1);
+	}
+
+	if (i >= 100) {
+		dev_err(&dev_priv->pdev->dev, "Failed to lock the DDR PLL\n");
+		return -ENODEV;
+	}
+
+	dev_info(&dev_priv->pdev->dev, "DDR40 PLL is locked after %d us\n", i);
+
+	/* Configure DDR40 VDL */
+	FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL);
+	FTHD_S2_REG_WRITE(0x103, S2_DDR40_PHY_VDL_CTL);
+
+	/* Poll for VDL calibration */
+	for (i = 0; i < 100; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS);
+		if (reg & 0x1)
+			break;
+		udelay(1);
+	}
+
+	if (reg & 0x1) {
+		dev_info(&dev_priv->pdev->dev,
+			 "First DDR40 VDL calibration completed after %d us",
+			 i);
+
+		if ((reg & 0x2) == 0) {
+			dev_info(&dev_priv->pdev->dev,
+				 "...but failed to lock\n");
+		}
+
+	} else {
+		dev_err(&dev_priv->pdev->dev,
+			"First DDR40 VDL calibration failed\n");
+	}
+
+	FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL);
+	FTHD_S2_REG_WRITE(0, S2_DDR40_PHY_VDL_CTL); /* Needed? */
+	FTHD_S2_REG_WRITE(0x200, S2_DDR40_PHY_VDL_CTL); /* calib steps */
+
+	for (i = 0; i < 1000; i++) {
+		reg = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_STATUS);
+		if (reg & 0x1)
+			break;
+		udelay(1);
+	}
+
+	dev_info(&dev_priv->pdev->dev,
+		 "Second DDR40 VDL calibration completed after %d us\n", i);
+
+	if (reg & 0x2) {
+		step_size = (reg & S2_DDR40_PHY_VDL_STEP_MASK) >>
+			    S2_DDR40_PHY_VDL_STEP_SHIFT;
+		dev_info(&dev_priv->pdev->dev, "Using step size %u\n",
+			 step_size);
+	} else {
+
+		val = 1000000 / dev_priv->ddr_speed;
+		step_size = (val * 0x4ec4ec4f) >> 22;
+		dev_info(&dev_priv->pdev->dev, "Using default step size (%u)\n",
+			 step_size);
+	}
+
+	dev_priv->vdl_step_size = step_size;
+
+	vdl_fine = FTHD_S2_REG_READ(S2_DDR40_PHY_VDL_CHAN_STATUS);
+
+	/* lock = 1 and byte_sel = 1 */
+	if ((vdl_fine & 2) == 0) {
+		vdl_fine = (vdl_fine >> 8) & 0x3f;
+		vdl_fine |= 0x10100;
+
+		FTHD_S2_REG_WRITE(vdl_fine, S2_DDR40_PHY_VDL_OVR_FINE);
+
+		vdl_coarse = 0x10000;
+
+		step_size >>= 4;
+		step_size += step_size * 2;
+
+		if (step_size > 10) {
+			step_size = (step_size + 118) >> 1;
+			step_size &= 0x3f;
+			step_size |= 0x10000;
+			vdl_coarse = step_size;
+		}
+
+		FTHD_S2_REG_WRITE(vdl_coarse, S2_DDR40_PHY_VDL_OVR_COARSE);
+
+		dev_info(&dev_priv->pdev->dev,
+			 "VDL set to: coarse=0x%x, fine=0x%x\n",
+			 vdl_coarse, vdl_fine);
+	}
+
+	/* Configure Virtual VTT connections and override */
+
+	vtt_cons = 0x1cf7fff;
+	FTHD_S2_REG_WRITE(vtt_cons, S2_DDR40_PHY_VTT_CONNECTIONS);
+
+	vtt_ovr = 0x77fff;
+	FTHD_S2_REG_WRITE(vtt_ovr, S2_DDR40_PHY_VTT_OVERRIDE);
+
+	FTHD_S2_REG_WRITE(0x4, S2_DDR40_PHY_VTT_CTL);
+
+	dev_info(&dev_priv->pdev->dev, "Virtual VTT enabled");
+
+	/* Process, Voltage and Temperature compensation */
+	FTHD_S2_REG_WRITE(0xc0fff, S2_DDR40_PHY_ZQ_PVT_COMP_CTL);
+	FTHD_S2_REG_WRITE(0x2, S2_DDR40_PHY_DRV_PAD_CTL);
+	FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_DRV_PAD_CTL);
+
+	val = 1000000 / dev_priv->ddr_speed;
+	reg = 4;
+
+	if (val >= 400) {
+		if (val > 900)
+			reg = 1;
+
+		reg += 5;
+	}
+
+	/* DDR read FIFO delay? */
+	FTHD_S2_REG_WRITE(reg, S2_DDR40_WL_RD_DATA_DLY);
+	FTHD_S2_REG_WRITE(0x2, S2_DDR40_WL_READ_CTL); /* le_adj, te_adj */
+	FTHD_S2_REG_WRITE(0x3, S2_DDR40_WL_WR_PREAMBLE_MODE); /* mode, long */
+
+	/* dq_oeb, dq_reb, dq_iddq, dq_rxenb */
+	reg = FTHD_S2_REG_READ(S2_DDR40_WL_IDLE_PAD_CTL);
+	FTHD_S2_REG_WRITE(reg & 0xff0fffff, S2_DDR40_WL_IDLE_PAD_CTL);
+	udelay(500);
+
+	FTHD_S2_REG_WRITE(0, S2_DDR_2004);
+	udelay(10000);
+
+	FTHD_S2_REG_WRITE(0xab0a, S2_DDR_2014);
+
+	/* Polling for BUSY */
+	ret = fthd_hw_ddr_status_busy(dev_priv, 10000, 10);
+	if (ret != 0)
+		return -EBUSY;
+
+	udelay(10000);
+
+	FTHD_S2_REG_WRITE(0, S2_3204);
+
+	/* Read DRAM mem address (FIXME: Need to mask a few bits here) */
+	reg = FTHD_S2_REG_READ(S2_DDR40_STRAP_STATUS);
+	dev_info(&dev_priv->pdev->dev,
+		 "S2 DRAM memory address: 0x%08x\n", reg);
+
+	switch (dev_priv->ddr_model) {
+	case 4:
+		val = 0x1fffffff;
+		break;
+	case 2:
+		val = 0x0fffffff;
+		break;
+	default:
+		/* Probably just invalid model */
+		val = dev_priv->ddr_model;
+	}
+
+	FTHD_S2_REG_WRITE(val, S2_3208);
+	FTHD_S2_REG_WRITE(0x1040, S2_3200);
+
+	fthd_hw_ddr_rewrite_mode_regs(dev_priv);
+
+	FTHD_S2_REG_WRITE(0x20000, S2_DDR_2014);
+	FTHD_S2_REG_WRITE(1, S2_DDR_2008);
+
+	return 0;
+}
+
+void fthd_ddr_phy_save_regs(struct fthd_private *dev_priv)
+{
+	u32 offset;
+	int i;
+
+	for (i = 0; i < DDR_PHY_NUM_REG; i++) {
+		offset = fthd_ddr_phy_reg_map[i];
+		dev_priv->ddr_phy_regs[i] =
+			FTHD_ISP_REG_READ(DDR_PHY_REG_BASE + offset);
+	}
+}
+
+void fthd_ddr_phy_restore_regs(struct fthd_private *dev_priv)
+{
+	u32 offset;
+	int i;
+
+	for (i = 0; i < DDR_PHY_NUM_REG; i++) {
+		offset = fthd_ddr_phy_reg_map[i];
+		FTHD_S2_REG_WRITE(dev_priv->ddr_phy_regs[i],
+				  DDR_PHY_REG_BASE + offset);
+	}
+}
+
+int fthd_irq_enable(struct fthd_private *dev_priv)
+{
+	FTHD_ISP_REG_WRITE(0xf8, ISP_IRQ_ENABLE);
+	pci_write_config_dword(dev_priv->pdev, 0x94, 0x200);
+
+	return 0;
+}
+
+int fthd_irq_disable(struct fthd_private *dev_priv)
+{
+	FTHD_ISP_REG_WRITE(0, ISP_IRQ_ENABLE);
+	pci_write_config_dword(dev_priv->pdev, 0x94, 0x0);
+
+	return 0;
+}
+
+int fthd_hw_init(struct fthd_private *dev_priv)
+{
+	int ret;
+
+	ret = fthd_hw_s2_init_pcie_link(dev_priv);
+	if (ret)
+		goto out;
+
+	fthd_hw_s2_preinit_ddr_controller_soc(dev_priv);
+	fthd_hw_s2_init_ddr_controller_soc(dev_priv);
+
+/*
+	dev_info(&dev_priv->pdev->dev,
+		 "Dumping DDR PHY reg map before shmoo\n");
+
+	for (i = 0; i < DDR_PHY_NUM_REGS; i++) {
+		if (!(i % 3) && i >  0)
+			printk("\n");
+
+		val = FTHD_S2_REG_READ(ddr_phy_reg_map[i]);
+		printk(KERN_CONT "0x%.3x = 0x%.8x\t",
+			 ddr_phy_reg_map[i], val);
+	}
+*/
+
+	ret = fthd_ddr_verify_mem(dev_priv, 0, MEM_VERIFY_NUM);
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev,
+			"Full memory verification failed! (%d)\n", ret);
+		/*
+		 * Here we should do a shmoo calibration but it's not yet
+		 * fully implemented.
+		 */
+
+		/* fthd_ddr_calibrate(dev_priv); */
+	} else {
+		dev_info(&dev_priv->pdev->dev,
+			 "Full memory verification succeeded! (%d)\n", ret);
+	}
+
+	/* Save our working configuration */
+	fthd_ddr_phy_save_regs(dev_priv);
+
+	FTHD_S2_REG_WRITE(0x8, S2_D108);
+	FTHD_S2_REG_WRITE(0xc, S2_D104);
+
+	FTHD_ISP_REG_WRITE(0, ISP_REG_40004);
+
+	ret = isp_init(dev_priv);
+	if (ret)
+	    goto out;
+
+	dev_info(&dev_priv->pdev->dev, "Enabling interrupts\n");
+	fthd_irq_enable(dev_priv);
+out:
+	return ret;
+}
+
+void fthd_hw_deinit(struct fthd_private *dev_priv)
+{
+	dev_info(&dev_priv->pdev->dev, "%s", __FUNCTION__);
+	FTHD_ISP_REG_WRITE(0, ISP_REG_41020);
+	fthd_irq_disable(dev_priv);
+}
diff --git a/drivers/media/pci/facetimehd/fthd_hw.h b/drivers/media/pci/facetimehd/fthd_hw.h
new file mode 100644
index 000000000000..58d802488f67
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_hw.h
@@ -0,0 +1,128 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#ifndef _FTHD_HW_H
+#define _FTHD_HW_H
+
+#include <linux/pci.h>
+
+/* Used after most PCI Link IO writes */
+static inline void fthd_hw_pci_post(struct fthd_private *dev_priv)
+{
+	pci_write_config_dword(dev_priv->pdev, 0, 0x12345678);
+}
+
+#define FTHD_S2_REG_READ(offset) _FTHD_S2_REG_READ(dev_priv, (offset))
+#define FTHD_S2_REG_WRITE(val, offset) _FTHD_S2_REG_WRITE(dev_priv, (val), (offset))
+
+#define FTHD_S2_MEM_READ(offset) _FTHD_S2_MEM_READ(dev_priv, (offset))
+#define FTHD_S2_MEM_WRITE(val, offset) _FTHD_S2_MEM_WRITE(dev_priv, (val), (offset))
+#define FTHD_S2_MEMCPY_TOIO(offset, buf, len) _FTHD_S2_MEMCPY_TOIO(dev_priv, (buf), (offset), (len))
+#define FTHD_S2_MEMCPY_FROMIO(buf, offset, len) _FTHD_S2_MEMCPY_FROMIO(dev_priv, (buf), (offset), (len))
+
+#define FTHD_ISP_REG_READ(offset) _FTHD_ISP_REG_READ(dev_priv, (offset))
+#define FTHD_ISP_REG_WRITE(val, offset) _FTHD_ISP_REG_WRITE(dev_priv, (val), (offset))
+
+static inline u32 _FTHD_S2_REG_READ(struct fthd_private *dev_priv, u32 offset)
+{
+	if (offset >= dev_priv->s2_io_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"S2 IO read out of range at %u\n", offset);
+		return 0;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "Link IO read at %u\n", offset);
+	return ioread32(dev_priv->s2_io + offset);
+}
+
+static inline void _FTHD_S2_REG_WRITE(struct fthd_private *dev_priv, u32 val,
+				      u32 offset)
+{
+	if (offset >= dev_priv->s2_io_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"S2 IO write out of range at %u\n", offset);
+		return;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "S2 IO write at %u\n", offset);
+	iowrite32(val, dev_priv->s2_io + offset);
+	fthd_hw_pci_post(dev_priv);
+}
+
+static inline u32 _FTHD_S2_MEM_READ(struct fthd_private *dev_priv, u32 offset)
+{
+	if (offset >= dev_priv->s2_mem_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"S2 MEM read out of range at %u\n", offset);
+		return 0;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "Link IO read at %u\n", offset);
+	return ioread32(dev_priv->s2_mem + offset);
+}
+
+static inline void _FTHD_S2_MEM_WRITE(struct fthd_private *dev_priv, u32 val,
+				      u32 offset)
+{
+	if (offset >= dev_priv->s2_mem_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"S2 MEM write out of range at %u\n", offset);
+		return;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "S2 IO write at %u\n", offset);
+	iowrite32(val, dev_priv->s2_mem + offset);
+}
+
+static inline void _FTHD_S2_MEMCPY_TOIO(struct fthd_private *dev_priv, const void *buf,
+					u32 offset, int len)
+{
+	memcpy_toio(dev_priv->s2_mem + offset, buf, len);
+}
+
+
+static inline void _FTHD_S2_MEMCPY_FROMIO(struct fthd_private *dev_priv, void *buf,
+					  u32 offset, int len)
+{
+	memcpy_fromio(buf, dev_priv->s2_mem + offset, len);
+}
+
+static inline u32 _FTHD_ISP_REG_READ(struct fthd_private *dev_priv, u32 offset)
+{
+	if (offset >= dev_priv->isp_io_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"ISP IO read out of range at %u\n", offset);
+		return 0;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "ISP IO read at %u\n", offset);
+	return ioread32(dev_priv->isp_io + offset);
+}
+
+static inline void _FTHD_ISP_REG_WRITE(struct fthd_private *dev_priv, u32 val,
+				       u32 offset)
+{
+	if (offset >= dev_priv->isp_io_len) {
+		dev_err(&dev_priv->pdev->dev,
+			"ISP IO write out of range at %u\n", offset);
+		return;
+	}
+
+	// dev_info(&dev_priv->pdev->dev, "Dev IO write at %u\n", offset);
+	iowrite32(val, dev_priv->isp_io + offset);
+	fthd_hw_pci_post(dev_priv);
+}
+
+extern int fthd_irq_enable(struct fthd_private *dev_priv);
+extern int fthd_irq_disable(struct fthd_private *dev_priv);
+extern int fthd_hw_init(struct fthd_private *dev_priv);
+extern void fthd_hw_deinit(struct fthd_private *priv);
+extern void fthd_ddr_phy_save_regs(struct fthd_private *dev_priv);
+extern void fthd_ddr_phy_restore_regs(struct fthd_private *dev_priv);
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_isp.c b/drivers/media/pci/facetimehd/fthd_isp.c
new file mode 100644
index 000000000000..bef1cd4b03c3
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_isp.c
@@ -0,0 +1,1459 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include <linux/dmi.h>
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_reg.h"
+#include "fthd_ringbuf.h"
+#include "fthd_isp.h"
+
+int isp_mem_init(struct fthd_private *dev_priv)
+{
+	struct resource *root = &dev_priv->pdev->resource[FTHD_PCI_S2_MEM];
+
+        dev_priv->mem = kzalloc(sizeof(struct resource), GFP_KERNEL);
+	if (!dev_priv->mem)
+	    return -ENOMEM;
+
+	dev_priv->mem->start = root->start;
+	dev_priv->mem->end = root->end;
+
+	/* Preallocate 8mb for the firmware */
+	dev_priv->firmware = isp_mem_create(dev_priv, FTHD_MEM_FIRMWARE,
+					    FTHD_MEM_FW_SIZE);
+
+	if (!dev_priv->firmware) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to preallocate firmware memory\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv,
+				   unsigned int type, resource_size_t size)
+{
+	struct isp_mem_obj *obj;
+	struct resource *root = dev_priv->mem;
+	int ret;
+
+	obj = kzalloc(sizeof(struct isp_mem_obj), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->type = type;
+	obj->base.name = "S2 ISP";
+	ret = allocate_resource(root, &obj->base, size, root->start, root->end,
+				PAGE_SIZE, NULL, NULL);
+	if (ret) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to allocate resource (size: %Ld, start: %Ld, end: %Ld)\n",
+			size, root->start, root->end);
+		kfree(obj);
+		obj = NULL;
+	}
+
+	obj->offset = obj->base.start - root->start;
+	obj->size = size;
+	obj->size_aligned = obj->base.end - obj->base.start;
+	return obj;
+}
+
+int isp_mem_destroy(struct isp_mem_obj *obj)
+{
+	if (obj) {
+		release_resource(&obj->base);
+		kfree(obj);
+		obj = NULL;
+	}
+
+	return 0;
+}
+
+static int isp_acpi_set_power(struct fthd_private *dev_priv, int power)
+{
+	acpi_status status;
+	acpi_handle handle;
+	struct acpi_object_list arg_list;
+	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object args[1];
+	union acpi_object *result;
+	int ret = 0;
+
+
+	handle = ACPI_HANDLE(&dev_priv->pdev->dev);
+	if(!handle) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to get S2 CMPE ACPI handle\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	args[0].type = ACPI_TYPE_INTEGER;
+	args[0].integer.value = power;
+
+	arg_list.count = 1;
+	arg_list.pointer = args;
+
+	status = acpi_evaluate_object(handle, "CMPE", &arg_list, &buffer);
+	if (ACPI_FAILURE(status)) {
+		dev_err(&dev_priv->pdev->dev,
+			"Failed to execute S2 CMPE ACPI method\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	result = buffer.pointer;
+
+	if (result->type != ACPI_TYPE_INTEGER || result->integer.value != 0) {
+		dev_err(&dev_priv->pdev->dev,
+			"Invalid ACPI response (len: %Ld)\n", buffer.length);
+		ret = -EINVAL;
+	}
+
+out:
+	kfree(buffer.pointer);
+	return ret;
+}
+
+static int isp_enable_sensor(struct fthd_private *dev_priv)
+{
+	int ret;
+
+	/* Power on sensor CMOS via SMC; some systems may not have CMPE */
+	ret = isp_acpi_set_power(dev_priv, 1);
+	if (ret)
+		dev_warn(&dev_priv->pdev->dev,
+			 "ACPI sensor power-on failed (%d), continuing\n", ret);
+
+	mdelay(100); /* wait for sensor power rail to stabilize */
+
+	return 0;
+}
+
+static int isp_load_firmware(struct fthd_private *dev_priv)
+{
+	const struct firmware *fw;
+	int ret = 0;
+
+	ret = request_firmware(&fw, "facetimehd/firmware.bin", &dev_priv->pdev->dev);
+	if (ret)
+		return ret;
+
+	/* Firmware memory is preallocated at init time */
+	if (!dev_priv->firmware)
+		return -ENOMEM;
+
+	if (dev_priv->firmware->base.start != dev_priv->mem->start) {
+		dev_err(&dev_priv->pdev->dev,
+			"Misaligned firmware memory object (offset: %lu)\n",
+			dev_priv->firmware->offset);
+		isp_mem_destroy(dev_priv->firmware);
+		dev_priv->firmware = NULL;
+		return -EBUSY;
+	}
+
+	FTHD_S2_MEMCPY_TOIO(dev_priv->firmware->offset, fw->data, fw->size);
+
+	/* Might need a flush here if we map ISP memory cached */
+
+	dev_info(&dev_priv->pdev->dev, "Loaded firmware, size: %lukb\n",
+		 fw->size / 1024);
+
+	release_firmware(fw);
+
+	return ret;
+}
+
+static void isp_free_channel_info(struct fthd_private *priv)
+{
+	struct fw_channel *chan;
+	int i;
+	for(i = 0; i < priv->num_channels; i++) {
+		chan = priv->channels[i];
+		if (!chan)
+			continue;
+
+		kfree(chan->name);
+		kfree(chan);
+		priv->channels[i] = NULL;
+	}
+	kfree(priv->channels);
+	priv->channels = NULL;
+}
+
+static struct fw_channel *isp_get_chan_index(struct fthd_private *priv, const char *name)
+{
+	int i;
+	for(i = 0; i < priv->num_channels; i++) {
+		if (!strcasecmp(priv->channels[i]->name, name))
+			return priv->channels[i];
+	}
+	return NULL;
+}
+
+static int isp_fill_channel_info(struct fthd_private *dev_priv, int offset, int num_channels)
+{
+	struct isp_channel_info info;
+	struct fw_channel *chan;
+	int i;
+
+	if (!num_channels)
+		return -EINVAL;
+
+	dev_priv->channels = kzalloc(num_channels * sizeof(struct fw_channel *), GFP_KERNEL);
+	if (!dev_priv->channels)
+		goto out;
+
+	dev_priv->num_channels = num_channels;
+
+	for(i = 0; i < num_channels; i++) {
+		FTHD_S2_MEMCPY_FROMIO(&info, offset + i * 256, sizeof(info));
+
+		chan = kzalloc(sizeof(struct fw_channel), GFP_KERNEL);
+		if (!chan)
+			goto out;
+
+		dev_priv->channels[i] = chan;
+
+		pr_debug("Channel %d: %s, type %d, source %d, size %d, offset %x\n",
+			 i, info.name, info.type, info.source, info.size, info.offset);
+
+		chan->name = kstrdup(info.name, GFP_KERNEL);
+		if (!chan->name)
+			goto out;
+
+		chan->type = info.type;
+		chan->source = info.source;
+		chan->size = info.size;
+		chan->offset = info.offset;
+		spin_lock_init(&chan->lock);
+		init_waitqueue_head(&chan->wq);
+	}
+
+	dev_priv->channel_terminal = isp_get_chan_index(dev_priv, "TERMINAL");
+	dev_priv->channel_debug = isp_get_chan_index(dev_priv, "DEBUG");
+	dev_priv->channel_shared_malloc = isp_get_chan_index(dev_priv, "SHAREDMALLOC");
+	dev_priv->channel_io = isp_get_chan_index(dev_priv, "IO");
+	dev_priv->channel_buf_h2t = isp_get_chan_index(dev_priv, "BUF_H2T");
+	dev_priv->channel_buf_t2h = isp_get_chan_index(dev_priv, "BUF_T2H");
+	dev_priv->channel_io_t2h = isp_get_chan_index(dev_priv, "IO_T2H");
+
+	if (!dev_priv->channel_terminal || !dev_priv->channel_debug
+	    || !dev_priv->channel_shared_malloc || !dev_priv->channel_io
+	    || !dev_priv->channel_buf_h2t || !dev_priv->channel_buf_t2h
+	    || !dev_priv->channel_io_t2h) {
+		dev_err(&dev_priv->pdev->dev, "did not find all of the required channels\n");
+		goto out;
+	}
+	return 0;
+out:
+	isp_free_channel_info(dev_priv);
+	return -ENOMEM;
+}
+
+static int fthd_isp_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf,
+			int request_len, int *response_len)
+{
+	struct isp_mem_obj *request;
+	struct isp_cmd_hdr cmd;
+	u32 address, request_size, response_size;
+	u32 entry;
+	int len, ret;
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	if (response_len) {
+		len = max(request_len, *response_len);
+	} else {
+		len = request_len;
+	}
+	len += sizeof(struct isp_cmd_hdr);
+
+	pr_debug("sending cmd %d to firmware\n", command);
+
+	request = isp_mem_create(dev_priv, FTHD_MEM_CMD, len);
+	if (!request) {
+		dev_err(&dev_priv->pdev->dev, "failed to allocate cmd memory object\n");
+		return -ENOMEM;
+	}
+
+	cmd.opcode = command;
+
+	FTHD_S2_MEMCPY_TOIO(request->offset, &cmd, sizeof(struct isp_cmd_hdr));
+	if (request_len)
+		FTHD_S2_MEMCPY_TOIO(request->offset + sizeof(struct isp_cmd_hdr), buf, request_len);
+
+	ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_io,
+					  request->offset, request_len + 8, (response_len ? *response_len : 0) + 8, &entry);
+	if (ret)
+		goto out;
+
+	if (entry == (u32)-1) {
+		ret = -EIO;
+		goto out;
+	}
+
+	if (command == CISP_CMD_POWER_DOWN) {
+		/* powerdown doesn't seem to generate a response */
+		ret = 0;
+		goto out;
+	}
+
+        ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_io, entry, 2000);
+	if (ret) {
+		if (response_len)
+			*response_len = 0;
+		goto out;
+	}
+
+	FTHD_S2_MEMCPY_FROMIO(&cmd, request->offset, sizeof(struct isp_cmd_hdr));
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS);
+	request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE);
+	response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE);
+
+	/* XXX: response size in the ringbuf is zero after command completion, how is buffer size
+	        verification done? */
+	if (response_len && *response_len)
+		FTHD_S2_MEMCPY_FROMIO(buf, (address & ~3) + sizeof(struct isp_cmd_hdr),
+				     *response_len);
+
+	pr_debug("status %04x, request_len %d response len %d address_flags %x\n", cmd.status,
+		request_size, response_size, address);
+
+	ret = cmd.status ? -EIO : 0;
+out:
+	isp_mem_destroy(request);
+	return ret;
+}
+
+int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf,
+			int request_len, int *response_len)
+{
+	struct isp_mem_obj *request;
+	struct isp_cmd_hdr cmd;
+	u32 address, request_size, response_size;
+	u32 entry;
+	int len, ret;
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	if (response_len) {
+		len = max(request_len, *response_len);
+	} else {
+		len = request_len;
+	}
+	len += sizeof(struct isp_cmd_hdr);
+
+	pr_debug("sending debug cmd %d to firmware\n", command);
+
+	request = isp_mem_create(dev_priv, FTHD_MEM_CMD, len);
+	if (!request) {
+		dev_err(&dev_priv->pdev->dev, "failed to allocate cmd memory object\n");
+		return -ENOMEM;
+	}
+
+	cmd.opcode = command;
+
+	FTHD_S2_MEMCPY_TOIO(request->offset, &cmd, sizeof(struct isp_cmd_hdr));
+	if (request_len)
+		FTHD_S2_MEMCPY_TOIO(request->offset + sizeof(struct isp_cmd_hdr), buf, request_len);
+
+	ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_debug,
+					  request->offset, request_len + 8, (response_len ? *response_len : 0) + 8, &entry);
+	if (ret)
+		goto out;
+
+	if (entry == (u32)-1) {
+		ret = -EIO;
+		goto out;
+	}
+
+        ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_debug, entry, 20000);
+	if (ret) {
+		if (response_len)
+			*response_len = 0;
+		goto out;
+	}
+
+	FTHD_S2_MEMCPY_FROMIO(&cmd, request->offset, sizeof(struct isp_cmd_hdr));
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS);
+	request_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE);
+	response_size = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE);
+
+	/* XXX: response size in the ringbuf is zero after command completion, how is buffer size
+	        verification done? */
+	if (response_len && *response_len)
+		FTHD_S2_MEMCPY_FROMIO(buf, (address & ~3) + sizeof(struct isp_cmd_hdr),
+				     *response_len);
+
+	pr_info("status %04x, request_len %d response len %d address_flags %x\n", cmd.status,
+		request_size, response_size, address);
+
+	ret = 0;
+out:
+	isp_mem_destroy(request);
+	return ret;
+}
+
+
+
+int fthd_isp_cmd_start(struct fthd_private *dev_priv)
+{
+	pr_debug("sending start cmd to firmware\n");
+	return fthd_isp_cmd(dev_priv, CISP_CMD_START, NULL, 0, NULL);
+}
+
+int fthd_isp_cmd_channel_start(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_channel_start cmd;
+	pr_debug("sending channel start cmd to firmware\n");
+
+	cmd.channel = 0;
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_START, &cmd, sizeof(cmd), NULL);
+}
+
+int fthd_isp_cmd_channel_stop(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_channel_stop cmd;
+
+	cmd.channel = 0;
+	pr_debug("sending channel stop cmd to firmware\n");
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_STOP, &cmd, sizeof(cmd), NULL);
+}
+
+int fthd_isp_cmd_stop(struct fthd_private *dev_priv)
+{
+	return fthd_isp_cmd(dev_priv, CISP_CMD_STOP, NULL, 0, NULL);
+}
+
+static int fthd_isp_cmd_powerdown(struct fthd_private *dev_priv)
+{
+	return fthd_isp_cmd(dev_priv, CISP_CMD_POWER_DOWN, NULL, 0, NULL);
+}
+
+static void isp_free_set_file(struct fthd_private *dev_priv)
+{
+	if (dev_priv->set_file)
+		isp_mem_destroy(dev_priv->set_file);
+}
+
+int isp_powerdown(struct fthd_private *dev_priv)
+{
+	int retries;
+	u32 reg;
+
+	FTHD_ISP_REG_WRITE(0xf7fbdff9, 0xc3000);
+	fthd_isp_cmd_powerdown(dev_priv);
+
+	for (retries = 0; retries < 100; retries++) {
+		reg = FTHD_ISP_REG_READ(0xc3000);
+		if (reg == 0x8042006)
+			break;
+		mdelay(10);
+	}
+
+	if (retries >= 100) {
+		dev_info(&dev_priv->pdev->dev, "deinit failed!\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+int isp_uninit(struct fthd_private *dev_priv)
+{
+	FTHD_ISP_REG_WRITE(0x00000000, 0x40004);
+	FTHD_ISP_REG_WRITE(0x00000000, ISP_IRQ_ENABLE);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc0008);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc000c);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc0010);
+	FTHD_ISP_REG_WRITE(0x00000000, 0xc1004);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc100c);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc1014);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc101c);
+	FTHD_ISP_REG_WRITE(0xffffffff, 0xc1024);
+	mdelay(1);
+
+	FTHD_ISP_REG_WRITE(0, 0xc0000);
+	FTHD_ISP_REG_WRITE(0, 0xc0004);
+	FTHD_ISP_REG_WRITE(0, 0xc0008);
+	FTHD_ISP_REG_WRITE(0, 0xc000c);
+	FTHD_ISP_REG_WRITE(0, 0xc0010);
+	FTHD_ISP_REG_WRITE(0, 0xc0014);
+	FTHD_ISP_REG_WRITE(0, 0xc0018);
+	FTHD_ISP_REG_WRITE(0, 0xc001c);
+	FTHD_ISP_REG_WRITE(0, 0xc0020);
+	FTHD_ISP_REG_WRITE(0, 0xc0024);
+
+	FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR);
+	isp_free_channel_info(dev_priv);
+	isp_free_set_file(dev_priv);
+	isp_mem_destroy(dev_priv->firmware);
+	kfree(dev_priv->mem);
+	return 0;
+}
+
+
+int fthd_isp_cmd_print_enable(struct fthd_private *dev_priv, int enable)
+{
+	struct isp_cmd_print_enable cmd;
+
+	cmd.enable = enable;
+
+	return fthd_isp_cmd(dev_priv, CISP_CMD_PRINT_ENABLE, &cmd, sizeof(cmd), NULL);
+}
+
+int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_set_loadfile cmd;
+	struct isp_mem_obj *file;
+	const struct firmware *fw;
+	const char *filename = NULL;
+	const char *vendor, *board;
+	int ret = 0;
+
+	pr_debug("set loadfile\n");
+
+	vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
+	board = dmi_get_system_info(DMI_BOARD_NAME);
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	switch(dev_priv->sensor_id1) {
+	case 0x164:
+		filename = "facetimehd/8221_01XX.dat";
+		break;
+	case 0x190:
+		filename = "facetimehd/1222_01XX.dat";
+		break;
+	case 0x8830:
+		filename = "facetimehd/9112_01XX.dat";
+		break;
+	case 0x9770:
+		if (vendor && board && !strcmp(vendor, "Apple Inc.") &&
+		    !strncmp(board, "MacBookAir", sizeof("MacBookAir")-1)) {
+			filename = "facetimehd/1771_01XX.dat";
+			break;
+		}
+
+		switch(dev_priv->sensor_id0) {
+		case 4:
+			filename = "facetimehd/1874_01XX.dat";
+			break;
+		default:
+			filename = "facetimehd/1871_01XX.dat";
+			break;
+		}
+		break;
+	case 0x9774:
+		switch(dev_priv->sensor_id0) {
+		case 4:
+			filename = "facetimehd/1674_01XX.dat";
+			break;
+		case 5:
+			filename = "facetimehd/1675_01XX.dat";
+			break;
+		default:
+			filename = "facetimehd/1671_01XX.dat";
+			break;
+		}
+		break;
+	default:
+		break;
+
+	}
+
+	if (!filename) {
+		pr_debug("no set file for sensorid %04x %04x found\n",
+			 dev_priv->sensor_id0, dev_priv->sensor_id1);
+		return 0;
+	}
+
+	/* The set file is allowed to be missing but we don't get calibration */
+	ret = request_firmware(&fw, filename, &dev_priv->pdev->dev);
+	if (ret)
+		return 0;
+
+	/* Firmware memory is preallocated at init time */
+	BUG_ON(dev_priv->set_file);
+
+	file = isp_mem_create(dev_priv, FTHD_MEM_SET_FILE, fw->size);
+	FTHD_S2_MEMCPY_TOIO(file->offset, fw->data, fw->size);
+
+	release_firmware(fw);
+
+	dev_priv->set_file = file;
+	pr_debug("set file: addr %08lx, size %d\n", file->offset, (int)file->size);
+	cmd.addr = file->offset;
+	cmd.length = file->size;
+	ret = fthd_isp_cmd(dev_priv, CISP_CMD_CH_SET_FILE_LOAD, &cmd, sizeof(cmd), NULL);
+	if (ret)
+		dev_warn(&dev_priv->pdev->dev,
+			 "set file load failed (%d), continuing without calibration\n", ret);
+	return 0;
+}
+
+int fthd_isp_cmd_channel_info(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_channel_info cmd;
+	int ret, len;
+
+	pr_debug("sending ch info\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	len = sizeof(cmd);
+	ret = fthd_isp_cmd(dev_priv, CISP_CMD_CH_INFO_GET, &cmd, sizeof(cmd), &len);
+	print_hex_dump_bytes("CHINFO ", DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd));
+	pr_debug("sensor id: %04x %04x\n", cmd.sensorid0, cmd.sensorid1);
+	pr_debug("sensor count: %d\n", cmd.sensor_count);
+	pr_debug("camera module serial number string: %s\n", cmd.camera_module_serial_number);
+	pr_debug("sensor serial number: %02X%02X%02X%02X%02X%02X%02X%02X\n",
+		 cmd.sensor_serial_number[0], cmd.sensor_serial_number[1],
+		 cmd.sensor_serial_number[2], cmd.sensor_serial_number[3],
+		 cmd.sensor_serial_number[4], cmd.sensor_serial_number[5],
+		 cmd.sensor_serial_number[6], cmd.sensor_serial_number[7]);
+	dev_priv->sensor_id0 = cmd.sensorid0;
+	dev_priv->sensor_id1 = cmd.sensorid1;
+	dev_priv->sensor_count = cmd.sensor_count;
+	return ret;
+}
+
+int fthd_isp_cmd_camera_config(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_config cmd;
+	int ret, len;
+
+	pr_debug("sending camera config\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	len = sizeof(cmd);
+	ret = fthd_isp_cmd(dev_priv, CISP_CMD_CONFIG_GET, &cmd, sizeof(cmd), &len);
+	if (!ret)
+		print_hex_dump_bytes("CAMINFO ", DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd));
+	return ret;
+}
+
+int fthd_isp_cmd_channel_camera_config(struct fthd_private *dev_priv)
+{
+	struct isp_cmd_channel_camera_config cmd;
+	int ret, len, i;
+	char prefix[16];
+	pr_debug("sending ch camera config\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	for(i = 0; i < dev_priv->sensor_count; i++) {
+		cmd.channel = i;
+
+		len = sizeof(cmd);
+		ret = fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_CONFIG_GET, &cmd, sizeof(cmd), &len);
+		if (ret)
+			break;
+		snprintf(prefix, sizeof(prefix)-1, "CAMCONF%d ", i);
+		print_hex_dump_bytes(prefix, DUMP_PREFIX_OFFSET, &cmd, sizeof(cmd));
+
+		/* The first two u16s of the config payload are the sensor's
+		 * native width and height (e.g. 1280x720 on MacBookPro,
+		 * 848x588 on the 12-inch MacBook). Record sensor 0's size so
+		 * the V4L2 layer can advertise the real resolution instead of
+		 * a hardcoded one. */
+		if (i == 0) {
+			unsigned int w = cmd.data[0] | (cmd.data[1] << 8);
+			unsigned int h = cmd.data[2] | (cmd.data[3] << 8);
+
+			if (w && h) {
+				dev_priv->sensor_width = w;
+				dev_priv->sensor_height = h;
+				pr_debug("sensor native resolution: %ux%u\n", w, h);
+			}
+		}
+	}
+	return ret;
+}
+
+int fthd_isp_cmd_channel_camera_config_select(struct fthd_private *dev_priv, int channel, int config)
+{
+	struct isp_cmd_channel_camera_config_select cmd;
+	int len;
+
+	pr_debug("set camera config: %d\n", config);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.config = config;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_CONFIG_SELECT, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_crop_set(struct fthd_private *dev_priv, int channel,
+				  int x1, int y1, int x2, int y2)
+{
+	struct isp_cmd_channel_set_crop cmd;
+	int len;
+
+	pr_debug("set crop: [%d, %d] -> [%d, %d]\n", x1, y1, x2, y2);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.x1 = x1;
+	cmd.y2 = y2;
+	cmd.x2 = x2;
+	cmd.y2 = y2;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CROP_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_output_config_set(struct fthd_private *dev_priv, int channel, int x, int y, int pixelformat)
+{
+	struct isp_cmd_channel_output_config cmd;
+	int len;
+
+	pr_debug("output config: [%d, %d]\n", x, y);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.x1 = x; /* Y size */
+	cmd.x2 = x * 2; /* Chroma size? */
+	cmd.x3 = x;
+	cmd.y1 = y;
+
+	/* pixel formats:
+	 * 0 - plane 0 Y plane 1 UV
+	   1 - YUYV
+	   2 - YVYU
+	*/
+	cmd.pixelformat = pixelformat;
+	cmd.unknown3 = 0;
+	cmd.unknown5 = 0x7ff;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_OUTPUT_CONFIG_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_recycle_mode(struct fthd_private *dev_priv, int channel, int mode)
+{
+	struct isp_cmd_channel_recycle_mode cmd;
+	int len;
+
+	pr_debug("set recycle mode %d\n", mode);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.mode = mode;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_buffer_return(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_buffer_return cmd;
+	int len;
+
+	pr_debug("buffer return\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RETURN, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_recycle_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_recycle_mode cmd;
+	int len;
+
+	pr_debug("start recycle\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_BUFFER_RECYCLE_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_drc_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_drc_start cmd;
+	int len;
+
+	pr_debug("start drc\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_DRC_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_tone_curve_adaptation_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_tone_curve_adaptation_start cmd;
+	int len;
+
+	pr_debug("tone curve adaptation start\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_sif_pixel_format(struct fthd_private *dev_priv, int channel, int param1, int param2)
+{
+	struct isp_cmd_channel_sif_format_set cmd;
+	int len;
+
+	pr_debug("set pixel format %d, %d\n", param1, param2);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.param1 = param1;
+	cmd.param2 = param2;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SIF_PIXEL_FORMAT_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_error_handling_config(struct fthd_private *dev_priv, int channel, int param1, int param2)
+{
+	struct isp_cmd_channel_camera_err_handle_config cmd;
+	int len;
+
+	pr_debug("set error handling config %d, %d\n", param1, param2);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.param1 = param1;
+	cmd.param2 = param2;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_CAMERA_ERR_HANDLE_CONFIG, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_streaming_mode(struct fthd_private *dev_priv, int channel, int mode)
+{
+	struct isp_cmd_channel_streaming_mode cmd;
+	int len;
+
+	pr_debug("set streaming mode %d\n", mode);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.mode = mode;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_STREAMING_MODE_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_frame_rate_min(struct fthd_private *dev_priv, int channel, int rate)
+{
+	struct isp_cmd_channel_frame_rate_set cmd;
+	int len;
+
+	pr_debug("set ae frame rate min %d\n", rate);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.rate = rate;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_FRAME_RATE_MIN_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_frame_rate_max(struct fthd_private *dev_priv, int channel, int rate)
+{
+	struct isp_cmd_channel_frame_rate_set cmd;
+	int len;
+
+	pr_debug("set ae frame rate max %d\n", rate);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.rate = rate;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_FRAME_RATE_MAX_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_ae_speed_set(struct fthd_private *dev_priv, int channel, int speed)
+{
+	struct isp_cmd_channel_ae_speed_set cmd;
+	int len;
+
+	pr_debug("set ae speed %d\n", speed);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.speed = speed;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_SPEED_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_ae_stability_set(struct fthd_private *dev_priv, int channel, int stability)
+{
+	struct isp_cmd_channel_ae_stability_set cmd;
+	int len;
+
+	pr_debug("set ae stability %d\n", stability);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.stability = stability;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_STABILITY_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_ae_stability_to_stable_set(struct fthd_private *dev_priv, int channel, int value)
+{
+	struct isp_cmd_channel_ae_stability_to_stable_set cmd;
+	int len;
+
+	pr_debug("set ae stability to stable %d\n", value);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.value = value;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_face_detection_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_face_detection_start cmd;
+	int len;
+
+	pr_debug("face detection start\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_face_detection_stop(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_face_detection_stop cmd;
+	int len;
+
+	pr_debug("face detection stop\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_STOP, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_face_detection_enable(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_face_detection_enable cmd;
+	int len;
+
+	pr_debug("face detection enable\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_ENABLE, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_face_detection_disable(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_face_detection_disable cmd;
+	int len;
+
+	pr_debug("face detection disable\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_FACE_DETECTION_DISABLE, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_temporal_filter_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_temporal_filter_start cmd;
+	int len;
+
+	pr_debug("temporal filter start\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_temporal_filter_stop(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_temporal_filter_stop cmd;
+	int len;
+
+	pr_debug("temporal filter stop\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_temporal_filter_enable(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_temporal_filter_enable cmd;
+	int len;
+
+	pr_debug("temporal filter enable\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_temporal_filter_disable(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_temporal_filter_disable cmd;
+	int len;
+
+	pr_debug("temporal filter disable\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_motion_history_start(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_motion_history_start cmd;
+	int len;
+
+	pr_debug("motion history start\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_MOTION_HISTORY_START, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_motion_history_stop(struct fthd_private *dev_priv, int channel)
+{
+	struct isp_cmd_channel_motion_history_stop cmd;
+	int len;
+
+	pr_debug("motion history stop\n");
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_ae_metering_mode_set(struct fthd_private *dev_priv, int channel, int mode)
+{
+	struct isp_cmd_channel_ae_metering_mode_set cmd;
+	int len;
+
+	pr_debug("ae metering mode %d\n", mode);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.mode = mode;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_APPLE_CH_AE_METERING_MODE_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_brightness_set(struct fthd_private *dev_priv, int channel, int brightness)
+{
+	struct isp_cmd_channel_brightness_set cmd;
+	int len;
+
+	pr_debug("set brightness %d\n", brightness);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.brightness = brightness;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_BRIGHTNESS_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_contrast_set(struct fthd_private *dev_priv, int channel, int contrast)
+{
+	struct isp_cmd_channel_contrast_set cmd;
+	int len;
+
+	pr_debug("set contrast %d\n", contrast);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.contrast = contrast;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_CONTRAST_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_saturation_set(struct fthd_private *dev_priv, int channel, int saturation)
+{
+	struct isp_cmd_channel_saturation_set cmd;
+	int len;
+
+	pr_debug("set saturation %d\n", saturation);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.contrast = saturation;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_SATURATION_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_hue_set(struct fthd_private *dev_priv, int channel, int hue)
+{
+	struct isp_cmd_channel_hue_set cmd;
+	int len;
+
+	pr_debug("set hue %d\n", hue);
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.channel = channel;
+	cmd.contrast = hue;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, CISP_CMD_CH_SCALER_HUE_SET, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_isp_cmd_channel_awb(struct fthd_private *dev_priv, int channel, int enable)
+{
+	struct isp_cmd_channel cmd;
+	enum fthd_isp_cmds op;
+	int len;
+
+	pr_debug("set awb %s\n", enable ? "on" : "off");
+
+	cmd.channel = channel;
+	op = enable ? CISP_CMD_CH_AWB_START : CISP_CMD_CH_AWB_STOP;
+	len = sizeof(cmd);
+	return fthd_isp_cmd(dev_priv, op, &cmd, sizeof(cmd), &len);
+}
+
+int fthd_start_channel(struct fthd_private *dev_priv, int channel)
+{
+	int ret, x1 = 0, x2 = 0, pixelformat;
+
+	ret = fthd_isp_cmd_channel_camera_config(dev_priv);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_camera_config_select(dev_priv, 0, 0);
+	if (ret)
+		return ret;
+
+	/* Crop the full sensor area. The 12-inch MacBook (MacBook8,1, sensor
+	 * 1675) reports an 848x588 sensor via CISP_CMD_CH_CAMERA_CONFIG_GET;
+	 * the old hardcoded 1280x720 crop exceeds that array and makes the
+	 * sensor interface throw SIF errors. Use the negotiated format size. */
+	x1 = 0;
+	x2 = dev_priv->fmt.fmt.width;
+
+	ret = fthd_isp_cmd_channel_crop_set(dev_priv, 0, x1, 0, x2,
+					    dev_priv->fmt.fmt.height);
+	if (ret)
+		return ret;
+
+	switch(dev_priv->fmt.fmt.pixelformat) {
+	case V4L2_PIX_FMT_YUYV:
+		pixelformat = 1;
+		break;
+	case V4L2_PIX_FMT_YVYU:
+		pixelformat = 2;
+		break;
+	case V4L2_PIX_FMT_NV16:
+		pixelformat = 0;
+		break;
+	default:
+		pixelformat = 1;
+		WARN_ON(1);
+	}
+	ret = fthd_isp_cmd_channel_output_config_set(dev_priv, 0,
+						     dev_priv->fmt.fmt.width,
+						     dev_priv->fmt.fmt.height,
+						     pixelformat);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_recycle_mode(dev_priv, 0, 1);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_recycle_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_ae_metering_mode_set(dev_priv, 0, 3);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_drc_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_tone_curve_adaptation_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_ae_speed_set(dev_priv, 0, 60);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_ae_stability_set(dev_priv, 0, 75);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_ae_stability_to_stable_set(dev_priv, 0, 8);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_sif_pixel_format(dev_priv, 0, 1, 1);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_error_handling_config(dev_priv, 0, 2, 1);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_face_detection_enable(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_face_detection_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_frame_rate_max(dev_priv, 0, dev_priv->frametime * 256);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_frame_rate_min(dev_priv, 0, dev_priv->frametime * 256);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_temporal_filter_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_motion_history_start(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_temporal_filter_enable(dev_priv, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_streaming_mode(dev_priv, 0, 0);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_brightness_set(dev_priv, 0, 0x80);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_contrast_set(dev_priv, 0, 0x80);
+	if (ret)
+		return ret;
+	ret = fthd_isp_cmd_channel_start(dev_priv);
+	if (ret)
+		return ret;
+	mdelay(1000); /* Needed to settle AE */
+	return 0;
+}
+
+int fthd_stop_channel(struct fthd_private *dev_priv, int channel)
+{
+	int ret;
+
+	ret = fthd_isp_cmd_channel_stop(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_buffer_return(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_face_detection_stop(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_face_detection_disable(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_temporal_filter_disable(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	ret = fthd_isp_cmd_channel_motion_history_stop(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	return fthd_isp_cmd_channel_temporal_filter_stop(dev_priv, 0);
+}
+
+int isp_init(struct fthd_private *dev_priv)
+{
+	struct isp_mem_obj *fw_queue, *heap, *fw_args;
+	struct isp_fw_args fw_args_data;
+	u32 num_channels, queue_size, heap_size, reg, offset;
+	int i, retries, ret;
+
+	ret = isp_mem_init(dev_priv);
+	if (ret)
+		return ret;
+
+	ret = isp_load_firmware(dev_priv);
+	if (ret)
+		return ret;
+
+	pci_set_power_state(dev_priv->pdev, PCI_D0);
+	mdelay(10);
+
+	isp_enable_sensor(dev_priv);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_QUEUE_CTRL);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_SIZE);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_ADDR);
+	FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE2);
+	FTHD_ISP_REG_WRITE(0, ISP_REG_C3018);
+	FTHD_ISP_REG_WRITE(0, ISP_REG_C301C);
+
+	FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR);
+
+	/*
+	 * Probably the IPC queue
+	 * FIXME: Check if we can do 64bit writes on PCIe
+	 */
+	for (i = ISP_FW_CHAN_START; i <= ISP_FW_CHAN_END; i += 8) {
+		FTHD_ISP_REG_WRITE(0xffffffff, i);
+		FTHD_ISP_REG_WRITE(0, i + 4);
+	}
+
+	FTHD_ISP_REG_WRITE(0x80000000, ISP_REG_40008);
+	FTHD_ISP_REG_WRITE(0x1, ISP_REG_40004);
+
+	for (retries = 0; retries < 1000; retries++) {
+		reg = FTHD_ISP_REG_READ(ISP_IRQ_STATUS);
+		if ((reg & 0xf0) > 0)
+			break;
+		mdelay(10);
+	}
+
+	if (retries >= 1000) {
+		dev_info(&dev_priv->pdev->dev, "Init failed! No wake signal\n");
+		return -EIO;
+	}
+
+	dev_info(&dev_priv->pdev->dev, "ISP woke up after %dms\n",
+		 (retries - 1) * 10);
+
+	FTHD_ISP_REG_WRITE(0xffffffff, ISP_IRQ_CLEAR);
+
+	num_channels = FTHD_ISP_REG_READ(ISP_FW_CHAN_CTRL);
+	queue_size = FTHD_ISP_REG_READ(ISP_FW_QUEUE_CTRL) + 1;
+
+	dev_info(&dev_priv->pdev->dev,
+		 "Number of IPC channels: %u, queue size: %u\n",
+		 num_channels, queue_size);
+
+	if (num_channels > 32) {
+		dev_info(&dev_priv->pdev->dev, "Too many IPC channels: %u\n",
+			 num_channels);
+		return -EIO;
+	}
+
+	fw_queue = isp_mem_create(dev_priv, FTHD_MEM_FW_QUEUE, queue_size);
+	if (!fw_queue)
+		return -ENOMEM;
+
+	/* Firmware heap max size is 4mb */
+	heap_size = FTHD_ISP_REG_READ(ISP_FW_HEAP_SIZE);
+
+	if (heap_size == 0) {
+		FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL);
+		FTHD_ISP_REG_WRITE(fw_queue->offset, ISP_FW_QUEUE_CTRL);
+		FTHD_ISP_REG_WRITE(dev_priv->firmware->size_aligned, ISP_FW_SIZE);
+		FTHD_ISP_REG_WRITE(0x10000000 - dev_priv->firmware->size_aligned,
+				   ISP_FW_HEAP_SIZE);
+		FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_ADDR);
+		FTHD_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE2);
+	} else {
+		/* Must be at least 0x1000 bytes */
+		heap_size = (heap_size < 0x1000) ? 0x1000 : heap_size;
+
+		if (heap_size > 0x400000) {
+			dev_info(&dev_priv->pdev->dev,
+				 "Firmware heap request size too big (%ukb)\n",
+				 heap_size / 1024);
+			return -ENOMEM;
+		}
+
+		dev_info(&dev_priv->pdev->dev, "Firmware requested heap size: %ukb\n",
+			 heap_size / 1024);
+
+		heap = isp_mem_create(dev_priv, FTHD_MEM_HEAP, heap_size);
+		if (!heap)
+			return -ENOMEM;
+
+		FTHD_ISP_REG_WRITE(0, ISP_FW_CHAN_CTRL);
+
+		/* Set IPC queue base addr */
+		FTHD_ISP_REG_WRITE(fw_queue->offset, ISP_FW_QUEUE_CTRL);
+
+		FTHD_ISP_REG_WRITE(FTHD_MEM_FW_SIZE, ISP_FW_SIZE);
+
+		FTHD_ISP_REG_WRITE(0x10000000 - FTHD_MEM_FW_SIZE, ISP_FW_HEAP_SIZE);
+
+		FTHD_ISP_REG_WRITE(heap->offset, ISP_FW_HEAP_ADDR);
+
+		FTHD_ISP_REG_WRITE(heap->size, ISP_FW_HEAP_SIZE2);
+
+		/* Set FW args */
+		fw_args = isp_mem_create(dev_priv, FTHD_MEM_FW_ARGS, sizeof(struct isp_fw_args));
+		if (!fw_args)
+			return -ENOMEM;
+
+		fw_args_data.__unknown = 2;
+		fw_args_data.fw_arg = 0;
+		fw_args_data.full_stats_mode = 0;
+
+		FTHD_S2_MEMCPY_TOIO(fw_args->offset, &fw_args_data, sizeof(fw_args_data));
+
+		FTHD_ISP_REG_WRITE(fw_args->offset, ISP_REG_C301C);
+
+		FTHD_ISP_REG_WRITE(0x10, ISP_REG_41020);
+
+		for (retries = 0; retries < 1000; retries++) {
+			reg = FTHD_ISP_REG_READ(ISP_IRQ_STATUS);
+			if ((reg & 0xf0) > 0)
+				break;
+			mdelay(10);
+		}
+
+		if (retries >= 1000) {
+			dev_info(&dev_priv->pdev->dev, "Init failed! No second int\n");
+			return -EIO;
+		} /* FIXME: free on error path */
+
+		dev_info(&dev_priv->pdev->dev, "ISP second int after %dms\n",
+			 (retries - 1) * 10);
+
+		offset = FTHD_ISP_REG_READ(ISP_FW_CHAN_CTRL);
+		dev_info(&dev_priv->pdev->dev, "Channel description table at %08x\n", offset);
+		ret = isp_fill_channel_info(dev_priv, offset, num_channels);
+		if (ret)
+			return ret;
+
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_terminal);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_io);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_debug);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_buf_h2t);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_buf_t2h);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_shared_malloc);
+		fthd_channel_ringbuf_init(dev_priv, dev_priv->channel_io_t2h);
+
+		FTHD_ISP_REG_WRITE(0x8042006, ISP_FW_HEAP_SIZE);
+
+		for (retries = 0; retries < 1000; retries++) {
+			reg = FTHD_ISP_REG_READ(ISP_FW_HEAP_SIZE);
+			if (!reg)
+				break;
+			mdelay(10);
+		}
+
+		if (retries >= 1000) {
+			dev_info(&dev_priv->pdev->dev, "Init failed! No magic value\n");
+			isp_uninit(dev_priv);
+			return -EIO;
+		} /* FIXME: free on error path */
+		dev_info(&dev_priv->pdev->dev, "magic value: %08x after %d ms\n", reg, (retries - 1) * 10);
+	}
+
+	return 0;
+}
diff --git a/drivers/media/pci/facetimehd/fthd_isp.h b/drivers/media/pci/facetimehd/fthd_isp.h
new file mode 100644
index 000000000000..b12108f65193
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_isp.h
@@ -0,0 +1,767 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#ifndef _ISP_H
+#define _ISP_H
+
+/* ISP memory types */
+#define FTHD_MEM_FIRMWARE	1
+#define FTHD_MEM_HEAP		2
+#define FTHD_MEM_FW_QUEUE	3
+#define FTHD_MEM_FW_ARGS        4
+#define FTHD_MEM_CMD            5
+#define FTHD_MEM_SHAREDMALLOC   6
+#define FTHD_MEM_SET_FILE       7
+#define FTHD_MEM_BUFFER         8
+
+#define FTHD_MEM_SIZE		0x8000000	/* 128mb */
+#define FTHD_MEM_FW_SIZE	0x800000	/* 8mb */
+
+enum fthd_isp_cmds {
+	CISP_CMD_START = 0x0,
+	CISP_CMD_STOP = 0x1,
+	CISP_CMD_RESET = 0x2,
+	CISP_CMD_CONFIG_GET = 0x3,
+	CISP_CMD_PRINT_ENABLE = 0x4,
+	CISP_CMD_REG_FILE_LOAD = 0x5,
+	CISP_CMD_BUILDINFO = 0x6,
+	CISP_CMD_TIMEPROFILE_START = 0x7,
+	CISP_CMD_TIMEPROFILE_STOP = 0x8,
+	CISP_CMD_TIMEPROFILE_SHOW = 0x9,
+	CISP_CMD_POWER_DOWN = 0xa,
+	CISP_CMD_CH_START = 0x100,
+	CISP_CMD_CH_STOP = 0x101,
+	CISP_CMD_CH_RESET = 0x102,
+	CISP_CMD_CH_STANDBY = 0x103,
+	CISP_CMD_CH_BUFFER_RETURN = 0x104,
+	CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET = 0x105,
+	CISP_CMD_CH_CAMERA_CONFIG_GET = 0x106,
+	CISP_CMD_CH_CAMERA_CONFIG_SELECT = 0x107,
+	CISP_CMD_CH_RAW_FRAME_PROCESS = 0x108,
+	CISP_CMD_CH_RAW_FRAME_PROCESS_START = 0x109,
+	CISP_CMD_CH_RAW_FRAME_PROCESS_STOP = 0x10a,
+	CISP_CMD_CH_I2C_READ = 0x10b,
+	CISP_CMD_CH_I2C_WRITE = 0x10c,
+	CISP_CMD_CH_INFO_GET = 0x10d,
+	CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET = 0x10e,
+	CISP_CMD_CH_BUFFER_RECYCLE_START = 0x10f,
+	CISP_CMD_CH_BUFFER_RECYCLE_STOP = 0x110,
+	CISP_CMD_CH_SET_FILE_LOAD = 0x111,
+	CISP_CMD_CH_CAPTURE_MODE_SET = 0x112,
+	CISP_CMD_CH_RAW_FRAME_PROCESS_GO = 0x113,
+	CISP_CMD_CH_EDGE_MAP_CONFIG_GET = 0x114,
+	CISP_CMD_CH_SIF_PIXEL_FORMAT_SET = 0x115,
+	CISP_CMD_CH_RPU_DMAOUT_CONFIG_GET = 0x116,
+	CISP_CMD_CH_RPU_DMAOUT_ENABLE = 0x117,
+	CISP_CMD_CH_RPU_DMAOUT_DISABLE = 0x118,
+	CISP_CMD_CH_CAMERA_MIPI_FREQ_CURRENT_GET = 0x119,
+	CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET = 0x11a,
+	CISP_CMD_CH_CAMERA_MIPI_FREQ_SELECT = 0x11b,
+	CISP_CMD_CH_ISO_PARAMS_SET = 0x11c,
+	CISP_CMD_CH_ISO_PARAMS_GET = 0x11d,
+	CISP_CMD_CH_CAMERA_PIX_FREQ_CURRENT_GET = 0x11e,
+	CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET = 0x11f,
+	CISP_CMD_CH_CAMERA_PIX_FREQ_SELECT = 0x120,
+	CISP_CMD_CH_CAMERA_ERR_COUNT_GET = 0x121,
+	CISP_CMD_CH_CAMERA_CLOCK_DIVISOR_SET = 0x122,
+	CISP_CMD_CH_CAMERA_CLOCK_DIVISOR_AUTO_MODE_SET = 0x123,
+	CISP_CMD_CH_CAMERA_ERR_HANDLE_CONFIG = 0x124,
+	CISP_CMD_CH_CAMERA_CHROMATIC_TYPE_SET = 0x125,
+	CISP_CMD_CH_CAMERA_CHROMATIC_TYPE_GET = 0x126,
+	CISP_CMD_CH_AE_START = 0x200,
+	CISP_CMD_CH_AE_STOP = 0x201,
+	CISP_CMD_CH_AE_AGC_PARAM_SET = 0x202,
+	CISP_CMD_CH_AE_BIAS_EXPOSURE_GET = 0x203,
+	CISP_CMD_CH_AE_BIAS_EXPOSURE_SET = 0x204,
+	CISP_CMD_CH_AE_CLIP_GET = 0x205,
+	CISP_CMD_CH_AE_CLIP_SET = 0x206,
+	CISP_CMD_CH_AE_FRAME_RATE_MAX_GET = 0x207,
+	CISP_CMD_CH_AE_FRAME_RATE_MAX_SET = 0x208,
+	CISP_CMD_CH_AE_FRAME_RATE_MIN_GET = 0x209,
+	CISP_CMD_CH_AE_FRAME_RATE_MIN_SET = 0x20a,
+	CISP_CMD_CH_AE_GAIN_CAP_GET = 0x20b,
+	CISP_CMD_CH_AE_GAIN_CAP_SET = 0x20c,
+	CISP_CMD_CH_AE_INTEGRATION_TIME_MAX_GET = 0x20d,
+	CISP_CMD_CH_AE_INTEGRATION_TIME_MAX_SET = 0x20e,
+	CISP_CMD_CH_AE_INTEGRATION_TIME_SET = 0x20f,
+	CISP_CMD_CH_AE_NOISE_REDUCTION_CONTROL_PARAM_GET = 0x210,
+	CISP_CMD_CH_AE_NOISE_REDUCTION_CONTROL_PARAM_SET = 0x211,
+	CISP_CMD_CH_AE_PARAM_GET = 0x212,
+	CISP_CMD_CH_AE_PRE_FRAME_RATE_GET = 0x213,
+	CISP_CMD_CH_AE_PRE_FRAME_RATE_SET = 0x214,
+	CISP_CMD_CH_AE_RED_EYE_PARAM_GET = 0x215,
+	CISP_CMD_CH_AE_RED_EYE_PARAM_SET = 0x216,
+	CISP_CMD_CH_AE_SPEED_GET = 0x217,
+	CISP_CMD_CH_AE_SPEED_SET = 0x218,
+	CISP_CMD_CH_AE_STABILITY_GET = 0x219,
+	CISP_CMD_CH_AE_STABILITY_SET = 0x21a,
+	CISP_CMD_CH_AE_STROBE_PARAM_GET = 0x21b,
+	CISP_CMD_CH_AE_STROBE_PARAM_SET = 0x21c,
+	CISP_CMD_CH_AE_WINDOW_PARAM_GET = 0x21d,
+	CISP_CMD_CH_AE_WINDOW_PARAM_SET = 0x21e,
+	CISP_CMD_CH_AE_SDGC_PARAM_SET = 0x21f,
+	CISP_CMD_CH_AE_DGC_PARAM_SET = 0x220,
+	CISP_CMD_CH_AE_LUX_CALC_PARAMS_SET = 0x221,
+	CISP_CMD_CH_AE_BRACKETING_PARAMS_SET = 0x222,
+	CISP_CMD_CH_AE_TARGET_GET = 0x223,
+	CISP_CMD_CH_AE_TARGET_SET = 0x224,
+	CISP_CMD_CH_AE_PREFLASH_PARAM_SET = 0x225,
+	CISP_CMD_CH_AE_UPDATE_SUSPEND = 0x226,
+	CISP_CMD_CH_AE_UPDATE_RESUME = 0x227,
+	CISP_CMD_CH_AE_STABILITY_TO_STABLE_GET = 0x228,
+	CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET = 0x229,
+	CISP_CMD_CH_AE_SENSOR_INTEGRATION_TIME_MIN_GET = 0x22a,
+	CISP_CMD_CH_AE_MANUAL_MODE_SET = 0x22b,
+	CISP_CMD_CH_AE_SENSOR_INTEGRATION_TIME_MAX_GET = 0x22c,
+	CISP_CMD_CH_AE_GAIN_CAP_MIN_GET = 0x22d,
+	CISP_CMD_CH_AE_GAIN_CAP_MIN_SET = 0x22e,
+	CISP_CMD_CH_AE_GAIN_CAP_MAX_WITH_EXP_GET = 0x22f,
+	CISP_CMD_CH_AE_GAIN_CAP_MAX_WITH_EXP_SET = 0x230,
+	CISP_CMD_CH_AE_GAIN_CAP_OFF_GET = 0x231,
+	CISP_CMD_CH_AE_GAIN_CAP_OFF_SET = 0x232,
+	CISP_CMD_CH_AE_BRACKETING_MANUAL_SET = 0x233,
+	CISP_CMD_CH_AE_BRACKETING_MODE_SET = 0x234,
+	CISP_CMD_CH_AE_MODE_SET = 0x235,
+	CISP_CMD_CH_AE_MODE_GET = 0x236,
+	CISP_CMD_CH_AE_PANO_LIMIT_SET = 0x237,
+	CISP_CMD_CH_AE_INTEGRATION_GAIN_SET = 0x238,
+	CISP_CMD_CH_AE_2WAYSPEED_SET = 0x239,
+	CISP_CMD_CH_AE_2WAYSPEED_GET = 0x23a,
+	CISP_CMD_CH_AWB_START = 0x300,
+	CISP_CMD_CH_AWB_STOP = 0x301,
+	CISP_CMD_CH_AWB_WINDOW_PARAM_GET = 0x302,
+	CISP_CMD_CH_AWB_WINDOW_PARAM_SET = 0x303,
+	CISP_CMD_CH_AWB_CCT_GET = 0x304,
+	CISP_CMD_CH_AWB_CCT_MANUAL = 0x305,
+	CISP_CMD_CH_AWB_CALIB_TABLE_SET = 0x306,
+	CISP_CMD_CH_AWB_BRACKETING_PARAMS_SET = 0x307,
+	CISP_CMD_CH_AWB_CCM_WARMUP_PARAMS_SET = 0x308,
+	CISP_CMD_CH_AWB_CCM_WARMUP_MATRIX_SET = 0x309,
+	CISP_CMD_CH_AWB_CCM_WARMUP_MATRIX_GET = 0x30a,
+	CISP_CMD_CH_AWB_2ND_GAIN_ADAPTIVE_THRESHOLDS_SET = 0x30b,
+	CISP_CMD_CH_AWB_2ND_GAIN_MANUAL = 0x30c,
+	CISP_CMD_CH_AWB_2ND_GAIN_GET = 0x30d,
+	CISP_CMD_CH_AWB_FLASH_GAIN_SET = 0x30e,
+	CISP_CMD_CH_AWB_UPDATE_SUSPEND = 0x30f,
+	CISP_CMD_CH_AWB_UPDATE_RESUME = 0x310,
+	CISP_CMD_CH_AWB_1ST_GAIN_MANUAL = 0x311,
+	CISP_CMD_CH_AF_START = 0x400,
+	CISP_CMD_CH_AF_STOP = 0x401,
+	CISP_CMD_CH_AF_EARLYOUT_GET = 0x402,
+	CISP_CMD_CH_AF_EARLYOUT_SET = 0x403,
+	CISP_CMD_CH_AF_FOCUS_POS_GET = 0x404,
+	CISP_CMD_CH_AF_SEARCH_POS_GET = 0x405,
+	CISP_CMD_CH_AF_SEARCH_POS_SET = 0x406,
+	CISP_CMD_CH_AF_ONE_SHOT = 0x407,
+	CISP_CMD_CH_AF_WINDOW_PARAM_GET = 0x408,
+	CISP_CMD_CH_AF_WINDOW_PARAM_SET = 0x409,
+	CISP_CMD_CH_AF_UPDATE_SUSPEND = 0x40a,
+	CISP_CMD_CH_AF_UPDATE_RESUME = 0x40b,
+	CISP_CMD_CH_AF_SOFTLANDING_SET = 0x40c,
+	CISP_CMD_CH_AF_SOFTLANDING_GET = 0x40d,
+	CISP_CMD_CH_SENSOR_FRAME_RATE_SET = 0x500,
+	CISP_CMD_CH_SENSOR_NVM_GET = 0x501,
+	CISP_CMD_CH_SENSOR_NVM_RELOAD = 0x502,
+	CISP_CMD_CH_SENSOR_TEST_PATTERN_CONFIG = 0x503,
+	CISP_CMD_CH_SENSOR_WARM_STARTUP_CONFIG = 0x504,
+	CISP_CMD_CH_SENSOR_CUSTOM_SETTING_CONFIG = 0x505,
+	CISP_CMD_CH_SENSOR_TEMPERATURE_GET = 0x506,
+	CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET = 0x507,
+	CISP_CMD_CH_SENSOR_PERMODULE_LSC_GET = 0x508,
+	CISP_CMD_CH_SENSOR_BLC_UPDATE_SUSPEND = 0x509,
+	CISP_CMD_CH_SENSOR_BLC_UPDATE_RESUME = 0x50a,
+	CISP_CMD_CH_SENSOR_POWER_ON = 0x50b,
+	CISP_CMD_CH_SENSOR_POWER_OFF = 0x50c,
+	CISP_CMD_CH_FOCUS_LIMITS_SET = 0x700,
+	CISP_CMD_CH_FOCUS_LIMITS_GET = 0x701,
+	CISP_CMD_CH_FOCUS_POSITION_SET = 0x702,
+	CISP_CMD_CH_FOCUS_POSITION_GET = 0x703,
+	CISP_CMD_CH_FOCUS_STEP_SIZE_SET = 0x704,
+	CISP_CMD_CH_FOCUS_STEP_SIZE_GET = 0x705,
+	CISP_CMD_CH_FOCUS_CAL_BITSHIFT_SET = 0x706,
+	CISP_CMD_CH_FOCUS_REINIT = 0x707,
+	CISP_CMD_CH_LED_TORCH_PARAM_GET = 0x600,
+	CISP_CMD_CH_LED_TORCH_PARAM_SET = 0x601,
+	CISP_CMD_CH_LED_TORCH_OFF = 0x602,
+	CISP_CMD_CH_LED_TORCH_ON = 0x603,
+	CISP_CMD_CH_LED_TORCH_ON_INDICATOR = 0x604,
+	CISP_CMD_CH_LED_TORCH_MANUAL_SET = 0x605,
+	CISP_CMD_CH_STATUS_LED_BrightnessMan_SET = 0x606,
+	CISP_CMD_CH_STATUS_LED_BrightnessMan_GET = 0x607,
+	CISP_CMD_CH_STATUS_LED_DEBUG_SET = 0x608,
+	CISP_CMD_CH_CROP_GET = 0x800,
+	CISP_CMD_CH_CROP_SET = 0x801,
+	CISP_CMD_CH_BPC_START = 0x802,
+	CISP_CMD_CH_BPC_STOP = 0x803,
+	CISP_CMD_CH_COLOR_CAL_DATA_SET = 0x804,
+	CISP_CMD_CH_COLOR_CAL_DATA_GET = 0x805,
+	CISP_CMD_CH_COLOR_CAL_IDEAL_SET = 0x806,
+	CISP_CMD_CH_COLOR_CAL_IDEAL_GET = 0x807,
+	CISP_CMD_CH_COLOR_CAL_ABS_GET = 0x808,
+	CISP_CMD_CH_COLOR_CAL_ABS_OVERRIDE = 0x809,
+	CISP_CMD_CH_SCALER_CROP_SET = 0x80a,
+	CISP_CMD_CH_COLOR_SATURATION_GET = 0xa00,
+	CISP_CMD_CH_COLOR_SATURATION_SET = 0xa01,
+	CISP_CMD_CH_TONE_CURVE_CUSTOM_GET = 0xa02,
+	CISP_CMD_CH_TONE_CURVE_CUSTOM_SET = 0xa03,
+	CISP_CMD_CH_COLOR_LSC_TABLE_SET = 0xa04,
+	CISP_CMD_CH_COLOR_LSC_START = 0xa05,
+	CISP_CMD_CH_COLOR_LSC_STOP = 0xa06,
+	CISP_CMD_CH_SCALER_SHARPNESS_SET = 0xa07,
+	CISP_CMD_CH_SCALER_SHARPNESS_GET = 0xa08,
+	CISP_CMD_CH_SHARPNESS_SET = 0xa09,
+	CISP_CMD_CH_SHARPNESS_GET = 0xa0a,
+	CISP_CMD_CH_NOISE_REDUCTION_SET = 0xa0b,
+	CISP_CMD_CH_NOISE_REDUCTION_GET = 0xa0c,
+	CISP_CMD_CH_CHROMA_SUPPRESSION_SET = 0xa0d,
+	CISP_CMD_CH_CHROMA_SUPPRESSION_GET = 0xa0e,
+	CISP_CMD_CH_HISTOGRAM_ENABLE = 0xa0f,
+	CISP_CMD_CH_COLOR_FULL_RES_LSC_TABLE_SET = 0xa10,
+	CISP_CMD_CH_COLOR_FULL_RES_LSC_ENABLE = 0xa11,
+	CISP_CMD_CH_COLOR_FULL_RES_LSC_DISABLE = 0xa12,
+	CISP_CMD_CH_KNOB_MANUAL_CONTROL_ENABLE = 0xa13,
+	CISP_CMD_CH_KNOB_MANUAL_CONTROL_DISABLE = 0xa14,
+	CISP_CMD_CH_BE_LASETTING_SET = 0xa15,
+	CISP_CMD_CH_BE_LASETTING_GET = 0xa16,
+	CISP_CMD_CH_BE_LA_INPUTMODE_SET = 0xa17,
+	CISP_CMD_CH_BE_LA_INPUTMODE_GET = 0xa18,
+	CISP_CMD_CH_DRC_SET = 0xa19,
+	CISP_CMD_CH_DRC_GET = 0xa1a,
+	CISP_CMD_CH_RPU_HISTOGRAM_PARAM_SET = 0xa1b,
+	CISP_CMD_CH_ALS_ENABLE = 0xa1c,
+	CISP_CMD_CH_ALS_DISABLE = 0xa1d,
+	CISP_CMD_CH_ALS_MODE_SET = 0xa1e,
+	CISP_CMD_CH_ALS_CCT_MANUAL = 0xa1f,
+	CISP_CMD_CH_ALS_POLYNOMIAL_SET = 0xa20,
+	CISP_CMD_CH_ALS_POLYNOMIAL_GET = 0xa21,
+	CISP_CMD_CH_COLOR_LSC_TABLE_GET = 0xa22,
+	CISP_CMD_CH_ALS_TEST_PATTERN_SET = 0xa23,
+	CISP_CMD_CH_BE_LA_SUSPEND = 0xa24,
+	CISP_CMD_CH_BE_LA_RESUME = 0xa25,
+	CISP_CMD_CH_COLOR_LSC_IDEAL_TABLE_SET = 0xa26,
+	CISP_CMD_CH_ALS_CCT_LIMIT_SET = 0xa27,
+	CISP_CMD_CH_TONE_CURVE_CUSTOM_BRACKETING_SET = 0xa28,
+	CISP_CMD_CH_MANUAL_BPC_GAIN_THRESHOLD_SET = 0xa29,
+	CISP_CMD_CH_COLOR_LSC_TABLE_SOURCE_SET = 0xa2a,
+	CISP_CMD_CH_ALS_SUSPEND = 0xa2b,
+	CISP_CMD_CH_ALS_RESUME = 0xa2c,
+	CISP_CMD_CH_OUTPUT_CONFIG_GET = 0xb00,
+	CISP_CMD_CH_OUTPUT_CONFIG_SET = 0xb01,
+	CISP_CMD_CH_SCALER_BRIGHTNESS_SET = 0xb02,
+	CISP_CMD_CH_SCALER_CONTRAST_SET = 0xb03,
+	CISP_CMD_CH_SCALER_SATURATION_SET = 0xb04,
+	CISP_CMD_CH_SCALER_HUE_SET = 0xb05,
+	CISP_CMD_CH_DRC_START = 0xc00,
+	CISP_CMD_CH_DRC_STOP = 0xc01,
+	CISP_CMD_CH_DRC_OFFLINE = 0xc02,
+	CISP_CMD_CH_DRC_OFFLINE_START = 0xc03,
+	CISP_CMD_CH_DRC_OFFLINE_STOP = 0xc04,
+	CISP_CMD_CH_FACE_DETECTION_START = 0xd00,
+	CISP_CMD_CH_FACE_DETECTION_STOP = 0xd01,
+	CISP_CMD_CH_FACE_DETECTION_CONFIG_GET = 0xd02,
+	CISP_CMD_CH_FACE_DETECTION_CONFIG_SET = 0xd03,
+	CISP_CMD_CH_FACE_DETECTION_DISABLE = 0xd04,
+	CISP_CMD_CH_FACE_DETECTION_ENABLE = 0xd05,
+	CISP_CMD_CH_FACE_DETECTION_INPUT_SET = 0xd06,
+	CISP_CMD_CH_FACE_DETECTION_IMAGE_ORIENTATION_SET = 0xd07,
+	CISP_CMD_CH_FACE_DETECTION_OFFLINE = 0xd08,
+	CISP_CMD_CH_FACE_DETECTION_OFFLINE_START = 0xd09,
+	CISP_CMD_CH_FACE_DETECTION_OFFLINE_STOP = 0xd0a,
+	CISP_CMD_CH_FACE_DETECTION_MODE_SET = 0xd0b,
+	CISP_CMD_CH_FACE_DETECTION_WINDOW_PARAM_SET = 0xd0c,
+	CISP_CMD_CH_FACE_DETECTION_WINDOW_PARAM_GET = 0xd0d,
+	CISP_CMD_CH_FRAMEDONE_TIMEOUT = 0xe00,
+	CISP_CMD_CH_FOCUS_DRIVER_INIT_FAILED = 0xe01,
+	CISP_CMD_CH_NVSTORAGE_INFO_GET = 0xf00,
+	CISP_CMD_CH_NVSTORAGE_DATA_GET = 0xf01,
+	CISP_CMD_CH_NVSTORAGE_DATA_SET = 0xf02,
+	CISP_CMD_APPLE_SET_FILE_LOAD = 0x8000,
+	CISP_CMD_APPLE_BUFFER_INFO_SET_GET = 0x8001,
+	CISP_CMD_APPLE_CH_HARDWARE_BLOCK_ENABLE = 0x8100,
+	CISP_CMD_APPLE_CH_HARDWARE_BLOCK_DISABLE = 0x8101,
+	CISP_CMD_APPLE_CH_CONTEXTSWITCH_ENABLE = 0x8102,
+	CISP_CMD_APPLE_CH_CONTEXTSWITCH_DISABLE = 0x8103,
+	CISP_CMD_APPLE_CH_SENSOR_NOISE_MODEL_SET = 0x8104,
+	CISP_CMD_APPLE_CH_SENSOR_NOISE_MODEL_GET = 0x8105,
+	CISP_CMD_APPLE_CH_STREAMING_MODE_SET = 0x8106,
+	CISP_CMD_APPLE_CH_STREAMING_MODE_GET = 0x8107,
+	CISP_CMD_APPLE_CH_AE_WINDOW_PARAM_SET = 0x8200,
+	CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_CONFIG_SET = 0x8201,
+	CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_START = 0x8202,
+	CISP_CMD_APPLE_CH_AE_DYNAMIC_SCENE_METERING_STOP = 0x8203,
+	CISP_CMD_APPLE_CH_AE_WINDOW_PARAM_GET = 0x8204,
+	CISP_CMD_APPLE_CH_AE_WINDOW_WEIGHT_SET = 0x8205,
+	CISP_CMD_APPLE_CH_AE_METERING_MODE_SET = 0x8206,
+	CISP_CMD_APPLE_CH_AE_METERING_MODE_GET = 0x8207,
+	CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_SET = 0x8208,
+	CISP_CMD_APPLE_CH_AE_MAX_FRAMERATE_GAIN_LIMIT_SET = 0x8209,
+	CISP_CMD_APPLE_CH_AE_MAX_FRAMERATE_GAIN_LIMIT_GET = 0x820a,
+	CISP_CMD_APPLE_CH_AE_TILES_MATRIX_METADATA_ENABLE = 0x820b,
+	CISP_CMD_APPLE_CH_AE_BINNING_GAIN_LUX_THRESHOLD_SET = 0x820c,
+	CISP_CMD_APPLE_CH_AE_PSEUDO_Y_WEIGHT_SET = 0x820d,
+	CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET = 0x820e,
+	CISP_CMD_APPLE_CH_AE_GAIN_CONVERGENCE_NORMALIZATION_SET = 0x820f,
+	CISP_CMD_APPLE_CH_AE_GAIN_CONVERGENCE_NORMALIZATION_GET = 0x8210,
+	CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_GET = 0x8211,
+	CISP_CMD_APPLE_CH_AWB_CCT_GET = 0x8300,
+	CISP_CMD_APPLE_CH_AWB_CCT_MANUAL = 0x8301,
+	CISP_CMD_APPLE_CH_AWB_WINDOW_PARAM_GET = 0x8302,
+	CISP_CMD_APPLE_CH_AWB_WINDOW_PARAM_SET = 0x8303,
+	CISP_CMD_APPLE_CH_AWB_CALIB_TABLE_SET = 0x8304,
+	CISP_CMD_APPLE_CH_AWB_SCHEME_SET = 0x8305,
+	CISP_CMD_APPLE_CH_AWB_SCHEME_GET = 0x8306,
+	CISP_CMD_APPLE_CH_AWB_HISTOGRAM_WEIGHT_SET = 0x8307,
+	CISP_CMD_APPLE_CH_AWB_LUXTABLE_PARAM_SET = 0x8308,
+	CISP_CMD_APPLE_CH_AWB_PROJECTION_POINT_SET = 0x8309,
+	CISP_CMD_APPLE_CH_AWB_HISTOGRAM_X_TO_CCT_LUT_SET = 0x830a,
+	CISP_CMD_APPLE_CH_AWB_2D_CCM_SET = 0x830b,
+	CISP_CMD_APPLE_CH_AWB_PRE_CCM_GAIN_GET = 0x830c,
+	CISP_CMD_APPLE_CH_AWB_CCM_GET = 0x830d,
+	CISP_CMD_APPLE_CH_AWB_TEMPORAL_COHERENCE_FILTER_SET = 0x830e,
+	CISP_CMD_APPLE_CH_AWB_SUSPEND_UPON_AE_STABLE_SET = 0x830f,
+	CISP_CMD_APPLE_CH_AWB_SUSPEND_UPON_AE_STABLE_GET = 0x8310,
+	CISP_CMD_APPLE_CH_AWB_POST_TINT_PARAM_SET = 0x8311,
+	CISP_CMD_APPLE_CH_AWB_MIX_LIGHTING_X_LOC_SET = 0x8312,
+	CISP_CMD_APPLE_CH_AWB_MIX_LIGHTING_CCM_SET = 0x8313,
+	CISP_CMD_APPLE_CH_AWB_TILE_STATS_Y_THRESHOLD_SET = 0x8314,
+	CISP_CMD_APPLE_CH_AWB_RATIO_SPACE_2ND_GAIN_THRESHOLD_SET = 0x8315,
+	CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_FILTER_V_SET = 0x8316,
+	CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_FILTER_H_SET = 0x8317,
+	CISP_CMD_APPLE_CH_AWB_HISTOGRAM_TRIM_SCALE_PROFILE_SET = 0x8318,
+	CISP_CMD_APPLE_CH_AWB_CCM_LUX_CLIP_SET = 0x8319,
+	CISP_CMD_APPLE_CH_AWB_MANUAL_WB_GAIN_SET = 0x831a,
+	CISP_CMD_APPLE_CH_AWB_CALIBRATION_MATRIX_GET = 0x831b,
+	CISP_CMD_APPLE_CH_AF_WINDOW_PARAM_GET = 0x8400,
+	CISP_CMD_APPLE_CH_AF_WINDOW_PARAM_SET = 0x8401,
+	CISP_CMD_APPLE_CH_AF_WINDOW_WEIGHT_GET = 0x8402,
+	CISP_CMD_APPLE_CH_AF_WINDOW_WEIGHT_SET = 0x8403,
+	CISP_CMD_APPLE_CH_AF_WINDOW_FD_CONFIG = 0x8404,
+	CISP_CMD_APPLE_CH_AF_PEAK_PREDICT_ENABLE_SET = 0x8405,
+	CISP_CMD_APPLE_CH_AF_FOCUS_POS_OVERRIDE_SET = 0x8406,
+	CISP_CMD_APPLE_CH_AF_PEAK_TRACKING_ENABLE = 0x8407,
+	CISP_CMD_APPLE_CH_AF_PEAK_TRACKING_START = 0x8408,
+	CISP_CMD_APPLE_CH_AF_FOCUS_MODE_SET = 0x8409,
+	CISP_CMD_APPLE_CH_AF_FOCUS_MODE_GET = 0x840a,
+	CISP_CMD_APPLE_CH_AF_MATRIX_MODE_CONFIG_SET = 0x840b,
+	CISP_CMD_APPLE_CH_AF_MATRIX_MODE_CONFIG_GET = 0x840c,
+	CISP_CMD_APPLE_CH_AF_MATRIX_MODE_DEBUG_GET = 0x840d,
+	CISP_CMD_APPLE_CH_AF_SCAN_HISTORY_GET = 0x840e,
+	CISP_CMD_APPLE_CH_FESTAT_CONFIG_GET = 0xc000,
+	CISP_CMD_APPLE_CH_TILE_REGION_SET = 0xc001,
+	CISP_CMD_APPLE_CH_TILE_WEIGHT_SET = 0xc002,
+	CISP_CMD_APPLE_CH_COLOR_LSC_TABLE_SET = 0xc003,
+	CISP_CMD_APPLE_CH_PIXEL_FILTER_TABLE_SET = 0xc004,
+	CISP_CMD_APPLE_CH_CSC_CONFIG_SET = 0xc005,
+	CISP_CMD_APPLE_CH_CSC_CONFIG_GET = 0xc006,
+	CISP_CMD_APPLE_CH_CSC2_CONFIG_SET = 0xc007,
+	CISP_CMD_APPLE_CH_CSC2_CONFIG_GET = 0xc008,
+	CISP_CMD_APPLE_CH_COLOR_HIST_CONFIG_SET = 0xc009,
+	CISP_CMD_APPLE_CH_COLOR_HIST_CONFIG_GET = 0xc00a,
+	CISP_CMD_APPLE_CH_CSC_GAMMA_SET = 0xc00b,
+	CISP_CMD_APPLE_CH_COLOR_HIST_CAPTURE = 0xc00c,
+	CISP_CMD_APPLE_CH_COLOR_LSC_IDEAL_TABLE_SET = 0xc00d,
+	CISP_CMD_APPLE_CH_STATPIXELDMAOUTPUT_SOURCE_SET = 0xc00e,
+	CISP_CMD_APPLE_CH_STATPIXELDMAOUTPUT_INFO_GET = 0xc00f,
+	CISP_CMD_APPLE_CH_AFFILTER_COEFF_SET = 0xc010,
+	CISP_CMD_APPLE_CH_AFFILTER_COEFF_GET = 0xc011,
+	CISP_CMD_APPLE_CH_EDGE_MAP_CONFIGURE = 0xc012,
+	CISP_CMD_APPLE_CH_AFHORZFILT_COEFF_SET = 0xc013,
+	CISP_CMD_APPLE_CH_AFHORZFILT_ENABLE_SET = 0xc014,
+	CISP_CMD_APPLE_CH_AFHORZFILT_SUMMODE_SET = 0xc015,
+	CISP_CMD_APPLE_CH_AFHORZFILT_THD_SET = 0xc016,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START = 0xc100,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP = 0xc101,
+	CISP_CMD_APPLE_CH_MOTION_HISTORY_START = 0xc102,
+	CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP = 0xc103,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_CONFIG_SET = 0xc104,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_CONFIG_GET = 0xc105,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_GAIN_SET = 0xc106,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_GAIN_GET = 0xc107,
+	CISP_CMD_APPLE_CH_MOTION_LUT_SET = 0xc108,
+	CISP_CMD_APPLE_CH_MOTION_LUT_GET = 0xc109,
+	CISP_CMD_APPLE_CH_LUMA_LUT_SET = 0xc10a,
+	CISP_CMD_APPLE_CH_LUMA_LUT_GET = 0xc10b,
+	CISP_CMD_APPLE_CH_TNR_MODE_SET = 0xc10c,
+	CISP_CMD_APPLE_CH_TNR_MODE_GET = 0xc10d,
+	CISP_CMD_APPLE_CH_TNR_PARAM_SET = 0xc10e,
+	CISP_CMD_APPLE_CH_TNR_PARAM_GET = 0xc10f,
+	CISP_CMD_APPLE_CH_TNR_INTERPOLATION_ENABLE = 0xc110,
+	CISP_CMD_APPLE_CH_TNR_AVERAGE_START = 0xc111,
+	CISP_CMD_APPLE_CH_TNR_AVERAGE_STOP = 0xc112,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE = 0xc113,
+	CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE = 0xc114,
+	CISP_CMD_APPLE_CH_TNR_AVERAGE_FRAME_COUNT_SET = 0xc115,
+	CISP_CMD_APPLE_CH_TNR_TUNING_PARAMS_SET = 0xc116,
+	CISP_CMD_APPLE_CH_TNR_LSC_GAIN_SET = 0xc117,
+	CISP_CMD_APPLE_CH_BINNING_COMPENSATION_FILTER_START = 0xc200,
+	CISP_CMD_APPLE_CH_BINNING_COMPENSATION_FILTER_STOP = 0xc201,
+	CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_START = 0xc300,
+	CISP_CMD_APPLE_CH_TONE_CURVE_ADAPTATION_STOP = 0xc301,
+	CISP_CMD_APPLE_CH_TONE_CURVE_PARAM_SET = 0xc302,
+	CISP_CMD_APPLE_CH_TONE_CURVE_PARAM_GET = 0xc303,
+	CISP_CMD_APPLE_CH_TONE_CURVE_UPDATE_SUSPEND = 0xc304,
+	CISP_CMD_APPLE_CH_TONE_CURVE_UPDATE_RESUME = 0xc305,
+	CISP_CMD_APPLE_CH_TONE_CURVE_MANUAL_CONTROL_ENABLE = 0xc306,
+	CISP_CMD_APPLE_CH_TONE_CURVE_MANUAL_CONTROL_DISABLE = 0xc307,
+	CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_TABLE_SET = 0xc308,
+	CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_TABLE_GET = 0xc309,
+	CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_LUXSCALE_SET = 0xc30a,
+	CISP_CMD_APPLE_CH_TONE_CURVE_LUX_ADAPTATION_LUXSCALE_GET = 0xc30b,
+	CISP_CMD_APPLE_CH_TONE_CURVE_STABILITY_SET = 0xc30c,
+	CISP_CMD_APPLE_CH_TONE_CURVE_STABILITY_GET = 0xc30d,
+	CISP_CMD_APPLE_CH_AUTO_HDR_HISTOGRAM_ENABLE = 0xc30e,
+	CISP_CMD_APPLE_CH_AUTO_HDR_HISTOGRAM_DISABLE = 0xc30f,
+	CISP_CMD_APPLE_CH_SNF_START = 0xc400,
+	CISP_CMD_APPLE_CH_SNF_STOP = 0xc401,
+	CISP_CMD_APPLE_CH_SNF_SET = 0xc402,
+	CISP_CMD_APPLE_CH_SNF_GET = 0xc403,
+	CISP_CMD_APPLE_CH_SNF_RADIAL_GAIN_SET = 0xc404,
+	CISP_CMD_APPLE_CH_DPC_ENABLE = 0xc500,
+	CISP_CMD_APPLE_CH_DPC_START = 0xc501,
+	CISP_CMD_APPLE_CH_DPC_STOP = 0xc502,
+	CISP_CMD_APPLE_CH_DPC_CTRLVALUE_OVERRIDE = 0xc503,
+	CISP_CMD_APPLE_CH_DPC_MAX_DYN_COUNT_OVERRIDE = 0xc504,
+	CISP_CMD_APPLE_CH_DPC_DYN_THRESHOLD0_OVERRIDE = 0xc505,
+	CISP_CMD_APPLE_CH_DPC_DYN_THRESHOLD1_OVERRIDE = 0xc506,
+	CISP_CMD_APPLE_CH_DPC_DESP_THRESHOLD0_OVERRIDE = 0xc507,
+	CISP_CMD_APPLE_CH_DPC_DESP_THRESHOLD1_OVERRIDE = 0xc508,
+	CISP_CMD_APPLE_CH_DPC_MAX_CORNER_OVERRIDE = 0xc509,
+	CISP_CMD_APPLE_CH_DPC_MAX_EDGE_OVERRIDE = 0xc50a,
+	CISP_CMD_APPLE_CH_DPC_MAX_CENTER_OVERRIDE = 0xc50b,
+	CISP_CMD_APPLE_CH_DPC_STATIC_DEFECTS_TABLE_SET = 0xc50c,
+};
+
+enum isp_debug_cmds {
+	CISP_CMD_DEBUG_BANNER=0,
+	CISP_CMD_DEBUG_NOP1,
+	CISP_CMD_DEBUG_NOP2,
+	CISP_CMD_DEBUG_PS,
+	CISP_CMD_DEBUG_GET_ROOT_HANDLE,
+	CISP_CMD_DEBUG_GET_OBJECT_BY_NAME,
+	CISP_CMD_DEBUG_GET_NUMBER_OF_CHILDREN,
+	CISP_CMD_DEBUG_GET_CHILDREN_BY_INDEX,
+	CISP_CMD_DEBUG_SHOW_OBJECT_GRAPH,
+	CISP_CMD_DEBUG_DUMP_OBJECT,
+	CISP_CMD_DEBUG_DUMP_ALL_OBJECTS,
+	CISP_CMD_DEBUG_GET_DEBUG_LEVEL,
+	CISP_CMD_DEBUG_SET_DEBUG_LEVEL,
+	CISP_CMD_DEBUG_SET_DEBUG_LEVEL_RECURSIVE,
+	CISP_CMD_DEBUG_GET_FSM_COUNT,
+	CISP_CMD_DEBUG_GET_FSM_BY_INDEX,
+	CISP_CMD_DEBUG_GET_FSM_BY_NAME,
+	CISP_CMD_DEBUG_GET_FSM_DEBUG_LEVEL,
+	CISP_CMD_DEBUG_SET_FSM_DEBUG_LEVEL,
+	CISP_CMD_DEBUG_FSM_UNKNOWN, /* XXX: don't know what this cmd is doing yet */
+	CISP_CMD_DEBUG_HEAP_STATISTICS,
+	CISP_CMD_DEBUG_IRQ_STATISTICS,
+	CISP_CMD_DEBUG_SHOW_SEMAPHORE_STATUS,
+	CISP_CMD_DEBUG_START_CPU_PERFORMANCE_COUNTER,
+	CISP_CMD_DEBUG_STOP_CPU_PERFORMANCE_COUNTER,
+	CISP_CMD_DEBUG_SHOW_WIRING_OPERATIONS,
+	CISP_CMD_DEBUG_SHOW_UNIT_TEST_STATUS,
+	CISP_CMD_DEBUG_GET_ENVIRONMENT,
+};
+
+struct isp_mem_obj {
+	struct resource base;
+	unsigned int type;
+	resource_size_t size;
+	resource_size_t size_aligned;
+	unsigned long offset;
+};
+
+struct isp_fw_args {
+	u32 __unknown;
+	u32 fw_arg;
+	u32 full_stats_mode;
+};
+
+struct isp_channel_info {
+	char name[64]; /* really that big? */
+	u32 type;
+	u32 source;
+	u32 size;
+	u32 offset;
+};
+
+struct isp_cmd_hdr {
+	u32 unknown0;
+	u16 opcode;
+	u16 status;
+} __attribute__((packed));
+
+struct isp_cmd_print_enable {
+	u32 enable;
+} __attribute__((packed));
+
+struct isp_cmd_config {
+	u32 field0;
+	u32 field4;
+	u32 field8;
+	u32 fieldc;
+	u32 field10;
+	u32 field14;
+	u32 field18;
+	u32 field1c;
+} __attribute__((packed));
+
+struct isp_cmd_set_loadfile {
+	u32 unknown;
+	u32 addr;
+	u32 length;
+} __attribute__((packed));
+
+struct isp_cmd_channel_info {
+	u32 field_0;
+	u32 field_4;
+	u32 field_8;
+	u32 field_c;
+	u16 sensorid0; /* field 10 */
+	u16 field_12;
+	u32 field_14;
+	u16 sensorid1; /* field 18 */
+	u16 field_1a;
+	u32 field_1c;
+	u32 field_20;
+	u8 unknown[52];
+	u32 sensor_count;
+	u8 unknown2[40];
+	u8 sensor_serial_number[8];
+	u8 camera_module_serial_number[18];
+} __attribute__((packed));
+
+struct isp_cmd_channel_camera_config {
+	u32 unknown;
+	u32 channel;
+	u8 data[88];
+};
+
+struct isp_cmd_channel_set_crop {
+	u32 channel;
+	u32 x1;
+	u32 y1;
+	u32 x2;
+	u32 y2;
+};
+
+struct isp_cmd_channel_output_config {
+	u32 channel;
+	u32 x1;
+	u32 y1;
+	u32 unknown3;
+	u32 pixelformat;
+	u32 x2;
+	u32 x3;
+	u32 unknown5;
+};
+
+struct isp_cmd_channel_recycle_mode {
+	u32 channel;
+	u32 mode;
+};
+
+struct isp_cmd_channel_camera_config_select {
+	u32 channel;
+	u32 config;
+};
+
+struct isp_cmd_channel_drc_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_tone_curve_adaptation_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_sif_format_set {
+	u32 channel;
+	u8 param1;
+	u8 param2;
+	u8 unknown0;
+	u8 unknown1;
+};
+
+struct isp_cmd_channel_camera_err_handle_config {
+	u32 channel;
+	u16 param1;
+	u16 param2;
+};
+
+struct isp_cmd_channel_streaming_mode {
+	u32 channel;
+	u16 mode;
+	u16 unknown;
+};
+
+struct isp_cmd_channel_frame_rate_set {
+	u32 channel;
+	u16 rate;
+};
+
+struct isp_cmd_channel_ae_speed_set {
+	u32 channel;
+	u16 speed;
+};
+
+struct isp_cmd_channel_ae_stability_set {
+	u32 channel;
+	u16 stability;
+};
+
+struct isp_cmd_channel_ae_stability_to_stable_set {
+	u32 channel;
+	u16 value;
+};
+
+struct isp_cmd_channel_face_detection_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_face_detection_stop {
+	u32 channel;
+};
+
+struct isp_cmd_channel_face_detection_enable {
+	u32 channel;
+};
+
+struct isp_cmd_channel_face_detection_disable {
+	u32 channel;
+};
+
+struct isp_cmd_channel_temporal_filter_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_temporal_filter_stop {
+	u32 channel;
+};
+
+struct isp_cmd_channel_temporal_filter_enable {
+	u32 channel;
+};
+
+struct isp_cmd_channel_temporal_filter_disable {
+	u32 channel;
+};
+
+struct isp_cmd_channel_motion_history_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_motion_history_stop {
+	u32 channel;
+};
+
+struct isp_cmd_channel_ae_metering_mode_set {
+	u32 channel;
+	u32 mode;
+};
+
+struct isp_cmd_channel_start {
+	u32 channel;
+};
+
+struct isp_cmd_channel_stop {
+	u32 channel;
+};
+
+struct isp_cmd_channel_brightness_set {
+	u32 channel;
+	u32 brightness;
+};
+
+struct isp_cmd_channel_contrast_set {
+	u32 channel;
+	u32 contrast;
+};
+
+struct isp_cmd_channel_saturation_set {
+	u32 channel;
+	u32 contrast;
+};
+
+struct isp_cmd_channel_hue_set {
+	u32 channel;
+	u32 contrast;
+};
+
+struct isp_cmd_channel {
+	u32 channel;
+};
+
+struct isp_cmd_channel_buffer_return {
+	u32 channel;
+};
+
+
+struct fthd_isp_debug_cmd {
+	u32 show_errors;
+	u32 arg[64];
+};
+
+#define to_isp_mem_obj(x) container_of((x), struct isp_mem_obj, base)
+
+extern int isp_init(struct fthd_private *dev_priv);
+extern int isp_uninit(struct fthd_private *dev_priv);
+
+extern int isp_mem_init(struct fthd_private *dev_priv);
+extern struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv,
+					  unsigned int type,
+					  resource_size_t size);
+extern int isp_mem_destroy(struct isp_mem_obj *obj);
+extern int fthd_isp_cmd_start(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_stop(struct fthd_private *dev_priv);
+extern int isp_powerdown(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_print_enable(struct fthd_private *dev_priv, int enable);
+extern int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_info(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_start(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_stop(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_camera_config(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_crop_set(struct fthd_private *dev_priv, int channel,
+					 int x1, int y1, int x2, int y2);
+extern int fthd_isp_cmd_channel_output_config_set(struct fthd_private *dev_priv, int channel, int x, int y, int pixelformat);
+extern int fthd_isp_cmd_channel_recycle_mode(struct fthd_private *dev_priv, int channel, int mode);
+extern int fthd_isp_cmd_channel_recycle_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_camera_config_select(struct fthd_private *dev_priv, int channel, int config);
+extern int fthd_isp_cmd_channel_drc_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_tone_curve_adaptation_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_sif_pixel_format(struct fthd_private *dev_priv, int channel, int param1, int param2);
+extern int fthd_isp_cmd_channel_error_handling_config(struct fthd_private *dev_priv, int channel, int param1, int param2);
+extern int fthd_isp_cmd_channel_streaming_mode(struct fthd_private *dev_priv, int channel, int mode);
+extern int fthd_isp_cmd_channel_frame_rate_min(struct fthd_private *dev_priv, int channel, int rate);
+extern int fthd_isp_cmd_channel_frame_rate_max(struct fthd_private *dev_priv, int channel, int rate);
+extern int fthd_isp_cmd_camera_config(struct fthd_private *dev_priv);
+extern int fthd_isp_cmd_channel_ae_speed_set(struct fthd_private *dev_priv, int channel, int speed);
+extern int fthd_isp_cmd_channel_ae_stability_set(struct fthd_private *dev_priv, int channel, int stability);
+extern int fthd_isp_cmd_channel_ae_stability_to_stable_set(struct fthd_private *dev_priv, int channel, int value);
+extern int fthd_isp_cmd_channel_face_detection_enable(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_face_detection_disable(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_face_detection_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_face_detection_stop(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_temporal_filter_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_temporal_filter_stop(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_temporal_filter_enable(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_temporal_filter_disable(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_motion_history_start(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_motion_history_stop(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_cmd_channel_ae_metering_mode_set(struct fthd_private *dev_priv, int channel, int mode);
+extern int fthd_isp_cmd_channel_brightness_set(struct fthd_private *dev_priv, int channel, int brightness);
+extern int fthd_isp_cmd_channel_contrast_set(struct fthd_private *dev_priv, int channel, int contrast);
+extern int fthd_isp_cmd_channel_saturation_set(struct fthd_private *dev_priv, int channel, int saturation);
+extern int fthd_isp_cmd_channel_hue_set(struct fthd_private *dev_priv, int channel, int hue);
+extern int fthd_isp_cmd_channel_awb(struct fthd_private *dev_priv, int channel, int hue);
+extern int fthd_isp_cmd_channel_buffer_return(struct fthd_private *dev_priv, int channel);
+extern int fthd_start_channel(struct fthd_private *dev_priv, int channel);
+extern int fthd_stop_channel(struct fthd_private *dev_priv, int channel);
+extern int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command, void *buf,
+			      int request_len, int *response_len);
+
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_reg.h b/drivers/media/pci/facetimehd/fthd_reg.h
new file mode 100644
index 000000000000..27da24a4a1d3
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_reg.h
@@ -0,0 +1,184 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
+ *
+ */
+
+#ifndef _FTHD_REG_H
+#define _FTHD_REG_H
+
+#include <linux/kernel.h>
+
+/* PCIE link regs */
+#define S2_PCIE_LINK_D000	0xd000
+#define S2_PCIE_LINK_D120	0xd120
+#define S2_PCIE_LINK_D124	0xd124
+#define S2_PCIE_LINK_D128	0xd128
+#define S2_PCIE_LINK_D12C	0xd12c
+
+/* Unknown */
+#define S2_D104			0xd104
+#define S2_D108			0xd108
+
+/* These are written to 0x203 before DDR soc init */
+#define S2_DDR_REG_1100		0x1100
+#define S2_DDR_REG_1104		0x1104
+#define S2_DDR_REG_1108		0x1108
+#define S2_DDR_REG_110C		0x110c
+#define S2_DDR_REG_1110		0x1110
+#define S2_DDR_REG_1114		0x1114
+#define S2_DDR_REG_1118		0x1118
+#define S2_DDR_REG_111C		0x111c
+
+#define S2_PLL_REFCLK		0x04
+#define S2_PLL_REFCLK_25MHZ	(1 << 3) /* 1 = 25MHz, 0 = 24MHz */
+
+#define S2_PLL_CMU_STATUS	0x0c	/* Register is called CMU_R_PLL_STS_MEMADDR */
+#define S2_PLL_CMU_STATUS_LOCKED (1 << 15) /* 1 = PLL locked, 0 = PLL not locked */
+
+#define S2_PLL_STATUS_A8	0xa8
+#define S2_PLL_BYPASS		(1 << 0) /* 1 = bypass, 0 = non-bypass */
+
+#define S2_PLL_CTRL_14		0x0014
+#define S2_PLL_CTRL_20		0x0020
+#define S2_PLL_CTRL_24		0x0024
+#define S2_PLL_CTRL_2C		0x002c
+#define S2_PLL_CTRL_9C		0x009c
+#define S2_PLL_CTRL_100		0x0100
+#define S2_PLL_CTRL_510		0x0510
+
+/* Probably DDR PHY PLL registers */
+#define S2_DDR_2004			0x2004
+#define S2_DDR_2008			0x2008
+#define S2_DDR_2014			0x2014
+#define S2_DDR_STATUS_2018		0x2018
+#define S2_DDR_STATUS_BUSY		(1 << 0)
+
+#define S2_DDR_20A0			0x20a0
+#define S2_DDR_20A4			0x20a4
+#define S2_DDR_20A8			0x20a8
+#define S2_DDR_20B0			0x20b0
+
+#define S2_20F8				0x20f8
+#define S2_DDR_2118			0x2118
+
+#define S2_2424				0x2424
+#define S2_2430				0x2430
+#define S2_2434				0x2434
+#define S2_2438				0x2438
+
+/* PLL for stage 2 */
+#define S2_DDR_PLL_STATUS_241C		0x241c
+#define S2_DDR_PLL_STATUS_241C_LOCKED	(1 << 10)
+
+/* PLL for stage 1 */
+#define S2_DDR_PLL_STATUS_2444		0x2444
+#define S2_DDR_PLL_STATUS_2444_LOCKED	(1 << 13)
+
+/*
+ * These registers must be saved and restored across suspend/resume
+ * FIXME: Double check these
+ */
+static const u32 fthd_ddr_phy_reg_map[] = {
+	0x0000, 0x0004, 0x0010, 0x0014, 0x0018, 0x001c, 0x0020, 0x0030,
+	0x0034, 0x0038, 0x003c, 0x0040, 0x0044, 0x0048, 0x004c, 0x0050,
+	0x0054, 0x0058, 0x005c, 0x0060, 0x0064, 0x0068, 0x006c, 0x0070,
+	0x0074, 0x0078, 0x007c, 0x0080, 0x0084, 0x0090, 0x0094, 0x0098,
+	0x009c, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x00b8, 0x00bc, 0x00c0,
+	0x0200, 0x0204, 0x0208, 0x020c, 0x0210, 0x0214, 0x0218, 0x021c,
+	0x0220, 0x0224, 0x0228, 0x022c, 0x0230, 0x0234, 0x0238, 0x023c,
+	0x0240, 0x0244, 0x0248, 0x024c, 0x0250, 0x0254, 0x0258, 0x025c,
+	0x0260, 0x0264, 0x0268, 0x026c, 0x0270, 0x0274, 0x02a4, 0x02a8,
+	0x02ac, 0x02b0, 0x02b4, 0x02b8, 0x02bc, 0x02c0, 0x02c4, 0x02c8,
+	0x02cc, 0x02d0, 0x02d4, 0x02d8, 0x02dc, 0x02e0, 0x02e4, 0x02e8,
+	0x02ec, 0x02f0, 0x02f4, 0x02f8, 0x02fc, 0x0300, 0x0304, 0x0308,
+	0x030c, 0x0310, 0x0314, 0x0328, 0x032c, 0x0330, 0x0334, 0x0338,
+	0x033c, 0x0348, 0x034c, 0x0350, 0x0354, 0x0358, 0x035c, 0x0360,
+	0x0364, 0x0370, 0x0374, 0x0378, 0x037c, 0x0380, 0x0384, 0x0388,
+	0x038c, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03a8, 0x03ac,
+};
+
+#define DDR_PHY_NUM_REG ARRAY_SIZE(fthd_ddr_phy_reg_map)
+#define DDR_PHY_REG_BASE		0x2800
+
+/* DDR40 */
+#define S2_DDR40_PHY_PLL_STATUS		0x2810
+#define S2_DDR40_PHY_PLL_STATUS_LOCKED	(1 << 0)
+#define S2_DDR40_PHY_PLL_CFG		0x2814
+#define S2_DDR40_PHY_PLL_DIV		0x281c
+#define S2_DDR40_PHY_AUX_CTL		0x2820
+
+#define S2_DDR40_PHY_VDL_OVR_COARSE	0x2830
+#define S2_DDR40_PHY_VDL_OVR_FINE	0x2834
+
+#define S2_DDR40_PHY_ZQ_PVT_COMP_CTL	0x283c
+#define S2_DDR40_PHY_DRV_PAD_CTL	0x2840
+
+#define S2_DDR40_PHY_VDL_CTL		0x2848
+
+#define S2_DDR40_PHY_VDL_STATUS		0x284c
+#define S2_DDR40_PHY_VDL_STEP_MASK	0x0ffc
+#define S2_DDR40_PHY_VDL_STEP_SHIFT	2
+
+#define S2_DDR40_PHY_DQ_CALIB_STATUS	0x2850
+#define S2_DDR40_PHY_VDL_CHAN_STATUS	0x2854
+
+#define S2_DDR40_PHY_VTT_CTL		0x285c
+#define S2_DDR40_PHY_VTT_STATUS		0x2860
+#define S2_DDR40_PHY_VTT_CONNECTIONS	0x2864
+#define S2_DDR40_PHY_VTT_OVERRIDE	0x2868
+
+#define S2_DDR40_STRAP_CTL		0x28b0
+#define S2_DDR40_STRAP_CTL_2		0x28b4
+#define S2_DDR40_STRAP_STATUS		0x28b8
+
+/* FIXME: Come up with a better name */
+#define S2_DDR40_BYTE_LANE_SIZE		0xa0
+#define S2_DDR40_NUM_BYTE_LANES		2
+
+#define S2_DDR40_RDEN_BYTE		0x2a00
+#define S2_DDR40_2A08			0x2a08
+#define S2_DDR40_2A0C			0x2a0c
+#define S2_DDR40_2A10			0x2a10
+#define S2_DDR40_2A34			0x2a34
+#define S2_DDR40_2A38			0x2a38
+#define S2_DDR40_RDEN_BYTE0		0x2a74
+#define S2_DDR40_2AA8			0x2aa8
+#define S2_DDR40_2AAC			0x2aac
+#define S2_DDR40_RDEN_BYTE1		0x2b14
+#define S2_DDR40_WL_RD_DATA_DLY		0x2b60
+#define S2_DDR40_WL_READ_CTL		0x2b64
+#define S2_DDR40_WL_READ_FIFO_STATUS	0x2b90
+#define S2_DDR40_WL_READ_FIFO_CLEAR	0x2b94
+#define S2_DDR40_WL_DRV_PAD_CTL		0x2ba4
+#define S2_DDR40_WL_CLK_PAD_DISABLE	0x2ba8
+#define S2_DDR40_WL_IDLE_PAD_CTL	0x2ba0
+#define S2_DDR40_WL_WR_PREAMBLE_MODE	0x2bac
+
+#define S2_3200				0x3200
+#define S2_3204				0x3204
+#define S2_3208				0x3208
+
+/* On iomem with pointer at 0x0ff0 (Bar 4: 1MB) */
+#define ISP_FW_CHAN_CTRL	0xc3000
+#define ISP_FW_QUEUE_CTRL	0xc3004
+#define ISP_FW_SIZE		0xc3008
+#define ISP_FW_HEAP_SIZE	0xc300c
+#define ISP_FW_HEAP_ADDR	0xc3010
+#define ISP_FW_HEAP_SIZE2	0xc3014
+#define ISP_REG_C3018		0xc3018 /* Module params or cmd buf? */
+#define ISP_REG_C301C		0xc301c
+#define ISP_REG_40004		0x40004
+#define ISP_REG_40008		0x40008
+#define ISP_IRQ_STATUS		0x41000
+#define ISP_IRQ_ENABLE		0x41004
+#define ISP_REG_41020		0x41020
+#define ISP_IRQ_CLEAR		0x41024
+
+#define ISP_FW_CHAN_START	0x0128
+#define ISP_FW_CHAN_END		0x0220
+
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_ringbuf.c b/drivers/media/pci/facetimehd/fthd_ringbuf.c
new file mode 100644
index 000000000000..7b099ae56379
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_ringbuf.c
@@ -0,0 +1,135 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_ringbuf.h"
+#include "fthd_isp.h"
+
+u32 get_entry_addr(struct fthd_private *dev_priv,
+			  struct fw_channel *chan, int num)
+{
+	return chan->offset + num * FTHD_RINGBUF_ENTRY_SIZE;
+}
+
+void fthd_channel_ringbuf_dump(struct fthd_private *dev_priv, struct fw_channel *chan)
+{
+	u32 entry;
+	char pos;
+	int i;
+
+	for( i = 0; i < chan->size; i++) {
+		if (chan->ringbuf.idx == i)
+			pos = '*';
+		else
+			pos = ' ';
+		entry = get_entry_addr(dev_priv, chan, i);
+	    pr_debug("%s: %c%3.3d: ADDRESS %08x REQUEST_SIZE %08x RESPONSE_SIZE %08x\n",
+		     chan->name, pos, i,
+		     FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS),
+		     FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_REQUEST_SIZE),
+		     FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_RESPONSE_SIZE));
+	}
+}
+
+void fthd_channel_ringbuf_init(struct fthd_private *dev_priv, struct fw_channel *chan)
+{
+	u32 entry;
+	int i;
+
+	chan->ringbuf.idx = 0;
+
+	if (chan->type == RINGBUF_TYPE_H2T) {
+		pr_debug("clearing ringbuf %s at %08x (size %d)\n",
+			 chan->name, chan->offset, chan->size);
+
+		spin_lock_irq(&chan->lock);
+		for(i = 0; i < chan->size; i++) {
+			entry = get_entry_addr(dev_priv, chan, i);
+			FTHD_S2_MEM_WRITE(1, entry + FTHD_RINGBUF_ADDRESS_FLAGS);
+			FTHD_S2_MEM_WRITE(0, entry + FTHD_RINGBUF_REQUEST_SIZE);
+			FTHD_S2_MEM_WRITE(0, entry + FTHD_RINGBUF_RESPONSE_SIZE);
+			entry += FTHD_RINGBUF_ENTRY_SIZE;
+		}
+		spin_unlock_irq(&chan->lock);
+	}
+}
+
+int fthd_channel_ringbuf_send(struct fthd_private *dev_priv, struct fw_channel *chan,
+			      u32 data_offset, u32 request_size, u32 response_size, u32 *entryp)
+{
+	u32 entry;
+
+	pr_debug("send %08x\n", data_offset);
+
+	spin_lock_irq(&chan->lock);
+	entry = get_entry_addr(dev_priv, chan, chan->ringbuf.idx);
+
+	if (++chan->ringbuf.idx >= chan->size)
+		chan->ringbuf.idx = 0;
+
+	if (!(FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0)) {
+		spin_unlock_irq(&chan->lock);
+		return -EAGAIN;
+	}
+
+	FTHD_S2_MEM_WRITE(request_size, entry + FTHD_RINGBUF_REQUEST_SIZE);
+	FTHD_S2_MEM_WRITE(response_size, entry + FTHD_RINGBUF_RESPONSE_SIZE);
+	wmb();
+	FTHD_S2_MEM_WRITE(data_offset | (chan->type == 0 ? 0 : 1),
+			  entry + FTHD_RINGBUF_ADDRESS_FLAGS);
+	spin_unlock_irq(&chan->lock);
+
+	spin_lock_irq(&dev_priv->io_lock);
+	FTHD_ISP_REG_WRITE(0x10 << chan->source, ISP_REG_41020);
+	spin_unlock_irq(&dev_priv->io_lock);
+	if (entryp)
+		*entryp = entry;
+	return 0;
+}
+
+u32 fthd_channel_ringbuf_receive(struct fthd_private *dev_priv,
+							struct fw_channel *chan)
+{
+	u32 entry, ret = (u32)-1;
+
+	spin_lock_irq(&chan->lock);
+
+	entry = get_entry_addr(dev_priv, chan, chan->ringbuf.idx);
+
+
+	if (!(FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0))
+		goto out;
+
+	ret = entry;
+
+	if (chan->type == FW_CHAN_TYPE_OUT && ++chan->ringbuf.idx >= chan->size)
+		chan->ringbuf.idx = 0;
+
+out:
+	spin_unlock_irq(&chan->lock);
+	return ret;
+}
+
+int fthd_channel_wait_ready(struct fthd_private *dev_priv, struct fw_channel *chan, u32 entry, int timeout)
+{
+	if (wait_event_interruptible_timeout(chan->wq,
+					     (FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & 1) ^ (chan->type != 0),
+		msecs_to_jiffies(timeout)) <= 0) {
+		dev_err(&dev_priv->pdev->dev, "%s: timeout\n", chan->name);
+		fthd_channel_ringbuf_dump(dev_priv, chan);
+		return -ETIMEDOUT;
+	}
+	return 0;
+}
diff --git a/drivers/media/pci/facetimehd/fthd_ringbuf.h b/drivers/media/pci/facetimehd/fthd_ringbuf.h
new file mode 100644
index 000000000000..a59fb5e0e21c
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_ringbuf.h
@@ -0,0 +1,44 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#ifndef _FTHD_RINGBUF_H
+#define _FTHD_RINGBUF_H
+
+#define FTHD_RINGBUF_ENTRY_SIZE 64
+
+#define FTHD_RINGBUF_ADDRESS_FLAGS 0
+#define FTHD_RINGBUF_REQUEST_SIZE 4
+#define FTHD_RINGBUF_RESPONSE_SIZE 8
+
+enum ringbuf_type_t {
+	RINGBUF_TYPE_H2T=0,
+	RINGBUF_TYPE_T2H=1,
+	RINGBUF_TYPE_UNIDIRECTIONAL,
+};
+
+struct fthd_ringbuf {
+	void *doorbell;
+	int idx;
+};
+
+struct fw_channel;
+struct fthd_private;
+extern void fthd_channel_ringbuf_dump(struct fthd_private *dev_priv, struct fw_channel *chan);
+extern void fthd_channel_ringbuf_init(struct fthd_private *dev_priv, struct fw_channel *chan);
+extern u32 fthd_channel_ringbuf_get_entry(struct fthd_private *, struct fw_channel *);
+extern int fthd_channel_ringbuf_send(struct fthd_private *dev_priv, struct fw_channel *chan,
+				     u32 data_offset, u32 request_size, u32 response_size, u32 *entry);
+
+extern u32 fthd_channel_ringbuf_receive(struct fthd_private *dev_priv,
+					struct fw_channel *chan);
+
+extern int fthd_channel_wait_ready(struct fthd_private *dev_priv, struct fw_channel *chan, u32 entry, int timeout);
+extern u32 get_entry_addr(struct fthd_private *dev_priv,
+			  struct fw_channel *chan, int num);
+#endif
diff --git a/drivers/media/pci/facetimehd/fthd_v4l2.c b/drivers/media/pci/facetimehd/fthd_v4l2.c
new file mode 100644
index 000000000000..11414473f7f0
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_v4l2.c
@@ -0,0 +1,790 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-sg.h>
+#include "fthd_drv.h"
+#include "fthd_hw.h"
+#include "fthd_isp.h"
+#include "fthd_ringbuf.h"
+#include "fthd_buffer.h"
+
+/* Fallback ceiling used only if the sensor's native size wasn't detected.
+ * The real per-device limit is dev_priv->sensor_width/height. */
+#define FTHD_MAX_WIDTH 1280
+#define FTHD_MAX_HEIGHT 720
+#define FTHD_MIN_WIDTH 320
+#define FTHD_MIN_HEIGHT 240
+#define FTHD_NUM_FORMATS 2 /* NV16 is disabled for now */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 7, 0)
+# define VFL_TYPE_VIDEO VFL_TYPE_GRABBER
+#endif
+
+static int fthd_buffer_queue_setup(
+    struct vb2_queue *vq,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)
+    const struct v4l2_format *fmt,
+#endif
+#if !(LINUX_VERSION_CODE >= KERNEL_VERSION(4,5,0))
+    const void *parg,
+#endif
+    unsigned int *nbuffers,
+    unsigned int *nplanes,
+    unsigned int sizes[],
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
+    struct device *alloc_devs[]
+#else
+    void *alloc_ctxs[]
+#endif
+) {
+
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vq);
+	struct v4l2_pix_format *cur_fmt = &dev_priv->fmt.fmt;
+	int i, total_size = 0;
+
+	if (*nplanes)
+		return sizes[0] < (cur_fmt->bytesperline * cur_fmt->height) ? -EINVAL : 0;
+
+	*nplanes = dev_priv->fmt.planes;
+
+	if (!*nplanes)
+		return -EINVAL;
+
+	/* FIXME: We assume single plane format here but not below */
+	for (i = 0; i < *nplanes; i++) {
+		sizes[i] = cur_fmt->sizeimage;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
+		alloc_devs[i] = &dev_priv->pdev->dev;
+#else
+		alloc_ctxs[i] = dev_priv->alloc_ctx;
+#endif
+		total_size += sizes[i];
+	}
+
+	*nbuffers = (4096 * 4096) / total_size;
+	if (*nbuffers > 4)
+		*nbuffers = 4;
+	if (*nbuffers <= 1)
+		return -ENOMEM;
+	pr_debug("using %d buffers\n", *nbuffers);
+
+	return 0;
+}
+
+static void fthd_buffer_cleanup(struct vb2_buffer *vb)
+{
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue);
+	struct h2t_buf_ctx *ctx = NULL;
+	int i;
+
+	pr_debug("%p\n", vb);
+	for(i = 0; i < FTHD_BUFFERS; i++) {
+		if (dev_priv->h2t_bufs[i].vb == vb) {
+			ctx = dev_priv->h2t_bufs + i;
+			break;
+		};
+	}
+	if (!ctx || ctx->state == BUF_FREE)
+		return;
+
+	ctx->state = BUF_FREE;
+	ctx->vb = NULL;
+	isp_mem_destroy(ctx->dma_desc_obj);
+	for(i = 0; i < dev_priv->fmt.planes; i++) {
+		iommu_free(dev_priv, ctx->plane[i]);
+		ctx->plane[i] = NULL;
+	}
+	ctx->dma_desc_obj = NULL;
+}
+
+static int fthd_send_h2t_buffer(struct fthd_private *dev_priv, struct h2t_buf_ctx *ctx)
+{
+	u32 entry;
+	int ret;
+
+	pr_debug("sending buffer %p size %ld, ctx %p\n", ctx->vb, sizeof(ctx->dma_desc_list), ctx);
+	FTHD_S2_MEMCPY_TOIO(ctx->dma_desc_obj->offset, &ctx->dma_desc_list, sizeof(ctx->dma_desc_list));
+	ret = fthd_channel_ringbuf_send(dev_priv, dev_priv->channel_buf_h2t,
+					ctx->dma_desc_obj->offset, 0x180, 0x30000000, &entry);
+
+	if (ret) {
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+		return ret;
+	}
+	return fthd_channel_wait_ready(dev_priv, dev_priv->channel_buf_h2t, entry, 2000);
+}
+
+static void fthd_buffer_queue(struct vb2_buffer *vb)
+{
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue);
+	struct dma_descriptor_list *list;
+	struct h2t_buf_ctx *ctx = NULL;
+
+	int i;
+	pr_debug("vb = %p\n", vb);
+	for(i = 0; i < FTHD_BUFFERS; i++) {
+		if (dev_priv->h2t_bufs[i].vb == vb) {
+			ctx = dev_priv->h2t_bufs + i;
+			break;
+		};
+	}
+
+	if (!ctx)
+		return;
+
+	if (ctx->state != BUF_ALLOC)
+		return;
+
+	if (!vb->vb2_queue->streaming) {
+		ctx->state = BUF_DRV_QUEUED;
+	} else {
+		list = &ctx->dma_desc_list;
+		list->field0 = 1;
+		ctx->state = BUF_HW_QUEUED;
+		wmb();
+		pr_debug("%d: field0: %d, count %d, pool %d, addr0 0x%08x, addr1 0x%08x tag 0x%08llx vb = %p\n", i, list->field0,
+			 list->desc[i].count, list->desc[i].pool, list->desc[i].addr0, list->desc[i].addr1, list->desc[i].tag, ctx->vb);
+
+		if (fthd_send_h2t_buffer(dev_priv, ctx)) {
+			vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+			ctx->state = BUF_ALLOC;
+		}
+	}
+	return;
+}
+
+static int fthd_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vb->vb2_queue);
+	struct sg_table *sgtable;
+	struct h2t_buf_ctx *ctx = NULL;
+	struct dma_descriptor_list *dma_list;
+	int i;
+
+	pr_debug("%p\n", vb);
+	for(i = 0; i < FTHD_BUFFERS; i++) {
+		if (dev_priv->h2t_bufs[i].state == BUF_FREE ||
+		    (dev_priv->h2t_bufs[i].state == BUF_ALLOC && dev_priv->h2t_bufs[i].vb == vb)) {
+			ctx = dev_priv->h2t_bufs + i;
+			break;
+		}
+	}
+
+	if (!ctx)
+		return -ENOBUFS;
+
+	if (ctx->state == BUF_FREE) {
+		pr_debug("allocating new entry\n");
+		ctx->dma_desc_obj = isp_mem_create(dev_priv, FTHD_MEM_BUFFER, 0x180);
+		if (!ctx->dma_desc_obj)
+			return -ENOMEM;
+
+		ctx->vb = vb;
+		ctx->state = BUF_ALLOC;
+
+		for(i = 0; i < dev_priv->fmt.planes; i++) {
+		  sgtable = vb2_dma_sg_plane_desc(vb, i);
+		  ctx->plane[i] = iommu_allocate_sgtable(dev_priv, sgtable);
+		  if(!ctx->plane[i])
+			  return -ENOMEM;
+		}
+	}
+
+	vb2_set_plane_payload(vb, 0, dev_priv->fmt.fmt.sizeimage);
+
+	dma_list = &ctx->dma_desc_list;
+	memset(dma_list, 0, 0x180);
+
+	dma_list->field0 = 1;
+	dma_list->count = 1;
+	dma_list->desc[0].count = 1;
+	dma_list->desc[0].pool = 0x02;
+	dma_list->desc[0].addr0 = (ctx->plane[0]->offset << 12) | 0xc0000000;
+
+	if (dev_priv->fmt.planes >= 2)
+		dma_list->desc[0].addr1 = (ctx->plane[1]->offset << 12) | 0xc0000000;
+	if (dev_priv->fmt.planes >= 3)
+		dma_list->desc[0].addr2 = (ctx->plane[2]->offset << 12) | 0xc0000000;
+
+	dma_list->desc[0].tag = (u64)ctx;
+	init_waitqueue_head(&ctx->wq);
+	return 0;
+}
+
+void fthd_buffer_return_handler(struct fthd_private *dev_priv, u32 offset, int size)
+{
+	struct dma_descriptor_list list;
+	struct h2t_buf_ctx *ctx;
+	int i;
+
+	FTHD_S2_MEMCPY_FROMIO(&list, offset, sizeof(list));
+
+	for(i = 0; i < list.count; i++) {
+		ctx = (struct h2t_buf_ctx *)list.desc[i].tag;
+		pr_debug("%d: field0: %d, count %d, pool %d, addr0 0x%08x, addr1 0x%08x tag 0x%08llx vb = %p, ctx = %p\n", i, list.field0,
+			 list.desc[i].count, list.desc[i].pool, list.desc[i].addr0, list.desc[i].addr1, list.desc[i].tag, ctx->vb, ctx);
+
+		if (ctx->state == BUF_HW_QUEUED || ctx->state == BUF_DRV_QUEUED) {
+			struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(ctx->vb);
+
+			vbuf->sequence = dev_priv->sequence++;
+			vbuf->vb2_buf.timestamp = ktime_get_ns();
+			vbuf->field = V4L2_FIELD_NONE;
+
+			ctx->state = BUF_ALLOC;
+			vb2_buffer_done(ctx->vb, VB2_BUF_STATE_DONE);
+		}
+
+	}
+}
+
+static int fthd_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vq);
+	struct h2t_buf_ctx *ctx;
+	int i, ret;
+
+	pr_debug("count = %d\n", count);
+	dev_priv->sequence = 0;
+
+	ret = fthd_start_channel(dev_priv, 0);
+	if (ret)
+		return ret;
+
+	for(i = 0; i < FTHD_BUFFERS && count; i++, count--) {
+		ctx = dev_priv->h2t_bufs + i;
+		if (ctx->state != BUF_DRV_QUEUED)
+			continue;
+
+		if (fthd_send_h2t_buffer(dev_priv, ctx)) {
+			vb2_buffer_done(ctx->vb, VB2_BUF_STATE_ERROR);
+			ctx->state = BUF_ALLOC;
+		}
+			ctx->state = BUF_HW_QUEUED;
+	}
+	return 0;
+}
+
+static void fthd_stop_streaming(struct vb2_queue *vq)
+{
+	struct fthd_private *dev_priv = vb2_get_drv_priv(vq);
+	struct h2t_buf_ctx *ctx;
+	int ret, i;
+
+	ret = fthd_stop_channel(dev_priv, 0);
+	if (!ret) {
+		pr_debug("waiting for buffers...\n");
+		vb2_wait_for_all_buffers(vq);
+		pr_debug("done\n");
+	} else {
+	    /* Firmware doesn't respond. */
+	    for(i = 0; i < FTHD_BUFFERS;i++) {
+		    ctx = dev_priv->h2t_bufs + i;
+		    if (ctx->state == BUF_DRV_QUEUED || ctx->state == BUF_HW_QUEUED) {
+			    vb2_buffer_done(ctx->vb, VB2_BUF_STATE_DONE);
+			    ctx->vb = NULL;
+			    ctx->state = BUF_ALLOC;
+		}
+	    }
+	}
+}
+
+static struct vb2_ops vb2_queue_ops = {
+	.queue_setup            = fthd_buffer_queue_setup,
+	.buf_prepare            = fthd_buffer_prepare,
+	.buf_cleanup            = fthd_buffer_cleanup,
+	.start_streaming        = fthd_start_streaming,
+	.stop_streaming         = fthd_stop_streaming,
+	.buf_queue              = fthd_buffer_queue,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(7, 0, 0)
+	.wait_prepare           = vb2_ops_wait_prepare,
+	.wait_finish            = vb2_ops_wait_finish,
+#endif
+};
+
+static struct v4l2_file_operations fthd_vdev_fops = {
+	.owner          = THIS_MODULE,
+	.open           = v4l2_fh_open,
+
+	.read		= vb2_fop_read,
+	.release        = vb2_fop_release,
+	.poll           = vb2_fop_poll,
+	.mmap           = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2
+};
+
+static int fthd_v4l2_ioctl_enum_input(struct file *filp, void *priv,
+				      struct v4l2_input *input)
+{
+	if (input->index != 0)
+		return -EINVAL;
+
+	memset(input, 0, sizeof(*input));
+	strcpy(input->name, "Camera");
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+	input->std = 0;
+
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_s_input(struct file *filp, void *priv, unsigned int i)
+{
+	if (i != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_querycap(struct file *filp, void *priv,
+				    struct v4l2_capability *cap)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+
+	strcpy(cap->driver, "facetimehd");
+	strcpy(cap->card, "Apple Facetime HD");
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
+		 pci_name(dev_priv->pdev));
+
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+			   V4L2_CAP_STREAMING;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_enum_fmt_vid_cap(struct file *filp, void *priv,
+				   struct v4l2_fmtdesc *fmt)
+{
+	char *desc = NULL;
+
+	switch (fmt->index) {
+	case 0:
+		fmt->pixelformat = V4L2_PIX_FMT_YUYV;
+		desc = "YUYV";
+		break;
+	case 1:
+		fmt->pixelformat = V4L2_PIX_FMT_YVYU;
+		desc = "YVYU";
+		break;
+	/* We don't support the mplane yet
+	case 2:
+		fmt->pixelformat = V4L2_PIX_FMT_NV16;
+		desc = "NV16";
+		break;
+	*/
+	default:
+		return -EINVAL;
+	}
+
+	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	strncpy(fmt->description, desc, sizeof(fmt->description));
+
+	return 0;
+}
+
+static int fthd_v4l2_adjust_format(struct fthd_private *dev_priv,
+				   struct v4l2_pix_format *pix)
+{
+
+	/* Upper bound is the sensor's native resolution (e.g. 1280x720 on
+	 * MacBookPro, 848x588 on the 12-inch MacBook); fall back to the generic
+	 * ceiling if it hasn't been detected yet. */
+	unsigned int max_w = dev_priv->sensor_width  ? : FTHD_MAX_WIDTH;
+	unsigned int max_h = dev_priv->sensor_height ? : FTHD_MAX_HEIGHT;
+
+	if (pix->pixelformat != V4L2_PIX_FMT_YUYV &&
+	    pix->pixelformat != V4L2_PIX_FMT_YVYU)
+		pix->pixelformat = V4L2_PIX_FMT_YUYV;
+
+	if (pix->width < FTHD_MIN_WIDTH)
+		pix->width = FTHD_MIN_WIDTH;
+	if (pix->width > max_w)
+		pix->width = max_w;
+	if (pix->height < FTHD_MIN_HEIGHT)
+		pix->height = FTHD_MIN_HEIGHT;
+	if (pix->height > max_h)
+		pix->height = max_h;
+
+	pix->colorspace = V4L2_COLORSPACE_SRGB;
+	pix->field = V4L2_FIELD_NONE;
+	pix->width = ALIGN(pix->width, 7);
+
+	switch (pix->pixelformat) {
+/*
+	case V4L2_PIX_FMT_NV16:
+		pix->sizeimage = pix->width * pix->height;
+		pix->bytesperline = pix->width;
+		break;
+*/
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	default:
+		pix->bytesperline = pix->width * 2;
+		pix->sizeimage = pix->bytesperline * pix->height;
+		break;
+	}
+
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_try_fmt_vid_cap(struct file *filp, void *_priv,
+					   struct v4l2_format *fmt)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+
+	pr_debug("%s: %dx%d\n", __FUNCTION__, fmt->fmt.pix.width, fmt->fmt.pix.height);
+
+	if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return fthd_v4l2_adjust_format(dev_priv, &fmt->fmt.pix);
+}
+
+static int fthd_v4l2_ioctl_g_fmt_vid_cap(struct file *filp, void *priv,
+					 struct v4l2_format *fmt)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+
+	pr_debug("%s\n", __FUNCTION__);
+	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	fmt->fmt.pix = dev_priv->fmt.fmt;
+
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_s_fmt_vid_cap(struct file *filp, void *priv,
+					 struct v4l2_format *fmt)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+	int ret;
+
+	if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	/* FIXME: Check if hardware is busy */
+
+	ret = fthd_v4l2_adjust_format(dev_priv, &fmt->fmt.pix);
+	if (ret)
+		return ret;
+
+	pr_debug("%c%c%c%c\n", fmt->fmt.pix.pixelformat, fmt->fmt.pix.pixelformat >> 8,
+		 fmt->fmt.pix.pixelformat >> 16, fmt->fmt.pix.pixelformat >> 24);
+
+	dev_priv->fmt.fmt = fmt->fmt.pix;
+
+	switch (fmt->fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_NV16:
+		dev_priv->fmt.planes = 2;
+		break;
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+		dev_priv->fmt.planes = 1;
+		break;
+	}
+
+	return 0;
+}
+
+
+static int fthd_v4l2_ioctl_g_parm(struct file *filp, void *priv,
+		struct v4l2_streamparm *parm)
+{
+	/* Report a consistent 30 fps, matching what the sensor actually delivers
+	 * and what enum_frameintervals advertises. The old frametime/1000 value
+	 * (25 fps) disagreed with the real 30 fps rate, which made GStreamer's
+	 * pipewiresrc compute negative frame durations and stall after one frame
+	 * (e.g. GNOME Snapshot froze, while ffplay/v4l2-ctl were unaffected). */
+	struct v4l2_fract timeperframe = {
+		.numerator = 1,
+		.denominator = 30,
+	};
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	parm->parm.capture.readbuffers = FTHD_BUFFERS;
+	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	parm->parm.capture.timeperframe = timeperframe;
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_s_parm(struct file *filp, void *priv,
+		struct v4l2_streamparm *parm)
+{
+
+        struct fthd_private *dev_priv = video_drvdata(filp);
+	struct v4l2_fract *timeperframe;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	timeperframe = &parm->parm.capture.timeperframe;
+
+	if(timeperframe->denominator == 0) {
+		timeperframe->numerator = 20;
+		timeperframe->denominator = 1000;
+	}
+
+	dev_priv->frametime = clamp_t(unsigned int, timeperframe->numerator * 1000 /
+				timeperframe->denominator, 20, 500);
+
+	return fthd_v4l2_ioctl_g_parm(filp, priv, parm);
+}
+
+static int fthd_v4l2_ioctl_enum_framesizes(struct file *filp, void *priv,
+		struct v4l2_frmsizeenum *sizes)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+
+	if (sizes->index)
+		return -EINVAL;
+
+	if (sizes->pixel_format != V4L2_PIX_FMT_YUYV &&
+	    sizes->pixel_format != V4L2_PIX_FMT_YVYU)
+		return -EINVAL;
+
+	sizes->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	sizes->discrete.width  = dev_priv->sensor_width  ? : FTHD_MAX_WIDTH;
+	sizes->discrete.height = dev_priv->sensor_height ? : FTHD_MAX_HEIGHT;
+
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_enum_frameintervals(struct file *filp, void *priv,
+		struct v4l2_frmivalenum *interval)
+{
+	struct fthd_private *dev_priv = video_drvdata(filp);
+	unsigned int max_w = dev_priv->sensor_width  ? : FTHD_MAX_WIDTH;
+	unsigned int max_h = dev_priv->sensor_height ? : FTHD_MAX_HEIGHT;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	if (interval->index)
+		return -EINVAL;
+
+	if (interval->pixel_format != V4L2_PIX_FMT_YUYV &&
+	    interval->pixel_format != V4L2_PIX_FMT_YVYU &&
+	    interval->pixel_format != V4L2_PIX_FMT_NV16)
+		return -EINVAL;
+
+	if (interval->width & 7
+	    || interval->width > max_w
+	    || interval->height > max_h)
+		return -EINVAL;
+
+	interval->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	interval->discrete.numerator = 1;
+	interval->discrete.denominator = 30;
+
+	return 0;
+}
+
+static int fthd_v4l2_ioctl_subscribe_event(struct v4l2_fh *fh,
+		const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	}
+
+	return -EINVAL;
+}
+
+static struct v4l2_ioctl_ops fthd_ioctl_ops = {
+	.vidioc_enum_input      = fthd_v4l2_ioctl_enum_input,
+	.vidioc_g_input         = fthd_v4l2_ioctl_g_input,
+	.vidioc_s_input         = fthd_v4l2_ioctl_s_input,
+	.vidioc_enum_fmt_vid_cap = fthd_v4l2_ioctl_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = fthd_v4l2_ioctl_try_fmt_vid_cap,
+
+	.vidioc_g_fmt_vid_cap   = fthd_v4l2_ioctl_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap   = fthd_v4l2_ioctl_s_fmt_vid_cap,
+	.vidioc_querycap        = fthd_v4l2_ioctl_querycap,
+
+
+        .vidioc_reqbufs         = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs     = vb2_ioctl_create_bufs,
+	.vidioc_querybuf        = vb2_ioctl_querybuf,
+	.vidioc_qbuf            = vb2_ioctl_qbuf,
+	.vidioc_dqbuf           = vb2_ioctl_dqbuf,
+	.vidioc_expbuf          = vb2_ioctl_expbuf,
+	.vidioc_streamon        = vb2_ioctl_streamon,
+	.vidioc_streamoff       = vb2_ioctl_streamoff,
+
+	.vidioc_g_parm          = fthd_v4l2_ioctl_g_parm,
+	.vidioc_s_parm          = fthd_v4l2_ioctl_s_parm,
+	.vidioc_enum_framesizes = fthd_v4l2_ioctl_enum_framesizes,
+	.vidioc_enum_frameintervals = fthd_v4l2_ioctl_enum_frameintervals,
+
+	.vidioc_subscribe_event	= fthd_v4l2_ioctl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static int fthd_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	pr_debug("id = %x\n", ctrl->id);
+	return -EINVAL;
+}
+
+static int fthd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct fthd_private *dev_priv = container_of(ctrl->handler, struct fthd_private, v4l2_ctrl_handler);
+	int ret = -EINVAL;
+
+	pr_info("id = %x, val = %d\n", ctrl->id, ctrl->val);
+
+	switch(ctrl->id) {
+	case V4L2_CID_CONTRAST:
+		ret = fthd_isp_cmd_channel_contrast_set(dev_priv, 0, ctrl->val);
+		break;
+	case V4L2_CID_BRIGHTNESS:
+		ret = fthd_isp_cmd_channel_brightness_set(dev_priv, 0, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		ret = fthd_isp_cmd_channel_saturation_set(dev_priv, 0, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		ret = fthd_isp_cmd_channel_hue_set(dev_priv, 0, ctrl->val);
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		ret = fthd_isp_cmd_channel_awb(dev_priv, 0, ctrl->val);
+
+	default:
+		break;
+
+	}
+	pr_debug("ret = %d\n", ret);
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops fthd_ctrl_ops = {
+	.g_volatile_ctrl = fthd_g_volatile_ctrl,
+	.s_ctrl = fthd_s_ctrl,
+};
+
+int fthd_v4l2_register(struct fthd_private *dev_priv)
+{
+	struct v4l2_device *v4l2_dev = &dev_priv->v4l2_dev;
+	struct video_device *vdev;
+	struct vb2_queue *q;
+	int ret;
+
+	ret = v4l2_device_register(&dev_priv->pdev->dev, v4l2_dev);
+	if (ret) {
+		pr_err("v4l2_device_register: %d\n", ret);
+		return ret;
+	}
+
+	vdev = video_device_alloc();
+	if (!vdev) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+	dev_priv->videodev = vdev;
+
+	q = &dev_priv->vb2_queue;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+	q->drv_priv = dev_priv;
+	q->ops = &vb2_queue_ops;
+	q->mem_ops = &vb2_dma_sg_memops;
+	q->buf_struct_size = 0;//sizeof(struct vpif_cap_buffer);
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,8,0)
+	q->min_buffers_needed = 1;
+#else
+	q->min_queued_buffers = 1;
+#endif
+	q->lock = &dev_priv->vb2_queue_lock;
+
+	ret = vb2_queue_init(q);
+	if (ret)
+		goto fail;
+
+	v4l2_ctrl_handler_init(&dev_priv->v4l2_ctrl_handler, 4);
+	v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, 0, 0xff, 1, 0x80);
+	v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops,
+			  V4L2_CID_CONTRAST, 0, 0xff, 1, 0x80);
+	v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops,
+			  V4L2_CID_SATURATION, 0, 0xff, 1, 0x80);
+	v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops,
+			  V4L2_CID_HUE, 0, 0xff, 1, 0x80);
+	v4l2_ctrl_new_std(&dev_priv->v4l2_ctrl_handler, &fthd_ctrl_ops,
+			  V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
+
+	if (dev_priv->v4l2_ctrl_handler.error) {
+		pr_err("failed to setup control handlers\n");
+		v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler);
+		goto fail;
+	}
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
+	dev_priv->alloc_ctx = vb2_dma_sg_init_ctx(&dev_priv->pdev->dev);
+#endif
+	vdev->v4l2_dev = v4l2_dev;
+	strcpy(vdev->name, "Apple Facetime HD"); // XXX: Length?
+	vdev->vfl_dir = VFL_DIR_RX;
+	vdev->fops = &fthd_vdev_fops;
+	vdev->ioctl_ops = &fthd_ioctl_ops;
+	vdev->queue = q;
+	vdev->release = video_device_release;
+	vdev->ctrl_handler = &dev_priv->v4l2_ctrl_handler;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,4,0)
+	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+			    V4L2_CAP_STREAMING;
+#endif
+	video_set_drvdata(vdev, dev_priv);
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		video_device_release(vdev);
+		goto fail_vdev;
+	}
+	/* Default to the sensor's native resolution (detected at probe), or the
+	 * generic ceiling if detection didn't run. */
+	dev_priv->fmt.fmt.width  = dev_priv->sensor_width  ? : FTHD_MAX_WIDTH;
+	dev_priv->fmt.fmt.height = dev_priv->sensor_height ? : FTHD_MAX_HEIGHT;
+	dev_priv->fmt.fmt.pixelformat = V4L2_PIX_FMT_YUYV;
+	dev_priv->fmt.fmt.sizeimage = dev_priv->fmt.fmt.width * dev_priv->fmt.fmt.height * 2;
+	dev_priv->fmt.planes = 1;
+
+	fthd_v4l2_adjust_format(dev_priv, &dev_priv->fmt.fmt);
+
+	return 0;
+fail_vdev:
+	v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler);
+fail:
+	v4l2_device_unregister(&dev_priv->v4l2_dev);
+	return ret;
+}
+
+void fthd_v4l2_unregister(struct fthd_private *dev_priv)
+{
+
+	v4l2_ctrl_handler_free(&dev_priv->v4l2_ctrl_handler);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
+	vb2_dma_sg_cleanup_ctx(dev_priv->alloc_ctx);
+#endif
+	video_unregister_device(dev_priv->videodev);
+	v4l2_device_unregister(&dev_priv->v4l2_dev);
+}
diff --git a/drivers/media/pci/facetimehd/fthd_v4l2.h b/drivers/media/pci/facetimehd/fthd_v4l2.h
new file mode 100644
index 000000000000..8e4f9d574904
--- /dev/null
+++ b/drivers/media/pci/facetimehd/fthd_v4l2.h
@@ -0,0 +1,34 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * FacetimeHD camera driver
+ *
+ * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
+ *
+ */
+
+#ifndef _FTHD_V4L2_H
+#define _FTHD_V4L2_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <media/v4l2-device.h>
+
+struct fthd_fmt {
+	struct v4l2_pix_format fmt;
+	const char *desc;
+	int range; /* CISP_COMMAND_CH_OUTPUT_CONFIG_SET */
+	int planes;
+	int x1; /* for CISP_CMD_CH_CROP_SET */
+	int y1;
+	int x2;
+	int y2;
+};
+
+struct fthd_private;
+extern int fthd_v4l2_register(struct fthd_private *dev_priv);
+extern void fthd_v4l2_unregister(struct fthd_private *dev_priv);
+
+#endif
-- 
2.55.0


  reply	other threads:[~2026-08-20 18:22 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-20 18:21 [RESEND PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
2026-08-20 18:21 ` Jack Flusche [this message]
2026-08-20 18:21 ` [RESEND PATCH 2/5] facetimehd: add Kconfig and Makefile Jack Flusche
2026-08-20 18:21 ` [RESEND PATCH 3/5] facetimehd: fix compile-time issues and formatting Jack Flusche
2026-08-20 18:21 ` [RESEND PATCH 4/5] facetimehd: add firmware extraction script from patjak/facetimehd-firmware Jack Flusche
2026-08-20 18:21 ` [RESEND PATCH 5/5] facetimehd: add entry to MAINTAINERS Jack Flusche
2026-08-20 19:37 ` [RESEND PATCH 0/5] Add driver for Broadcom FacetimeHD camera Patrik Jakobsson
2026-08-20 20:13   ` Jack Flusche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260820182156.18078-2-jackflusche@gmail.com \
    --to=jackflusche@gmail.com \
    --cc=hverkuil@kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=patrik.r.jakobsson@gmail.com \
    --cc=sakari.ailus@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox