Linux Media Controller development
 help / color / mirror / Atom feed
* [PATCH 0/5] Add driver for Broadcom FacetimeHD camera
@ 2026-08-13 17:35 Jack Flusche
  2026-08-13 17:35 ` [PATCH 1/5] facetimehd: copy code from patjak/facetimehd Jack Flusche
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

Merge Patrik Jakobsson's out-of-tree driver for the Broadcom FacetimeHD
camera on some Apple Macbooks using the Video4Linux 2 framework to add
in-tree support and remove the need to install a DKMS package

Jack Flusche (5):
  facetimehd: copy code from patjak/facetimehd
  facetimehd: add Kconfig and Makefile
  facetimehd: fix compile-time issues and formatting
  facetimehd: add firmware extraction script from
    patjak/facetimehd-firmware
  facetimehd: add entry to MAINTAINERS

 MAINTAINERS                                 |    7 +
 drivers/media/pci/Kconfig                   |    1 +
 drivers/media/pci/Makefile                  |    1 +
 drivers/media/pci/facetimehd/FIRMWARE       |    6 +
 drivers/media/pci/facetimehd/Kconfig        |   12 +
 drivers/media/pci/facetimehd/Makefile       |    2 +
 drivers/media/pci/facetimehd/firmware.sh    |   61 +
 drivers/media/pci/facetimehd/fthd_buffer.c  |  126 ++
 drivers/media/pci/facetimehd/fthd_buffer.h  |   73 +
 drivers/media/pci/facetimehd/fthd_ddr.c     |  670 +++++++++
 drivers/media/pci/facetimehd/fthd_ddr.h     |   33 +
 drivers/media/pci/facetimehd/fthd_debugfs.c |  204 +++
 drivers/media/pci/facetimehd/fthd_debugfs.h |   16 +
 drivers/media/pci/facetimehd/fthd_drv.c     |  558 +++++++
 drivers/media/pci/facetimehd/fthd_drv.h     |  124 ++
 drivers/media/pci/facetimehd/fthd_hw.c      |  710 +++++++++
 drivers/media/pci/facetimehd/fthd_hw.h      |  128 ++
 drivers/media/pci/facetimehd/fthd_isp.c     | 1468 +++++++++++++++++++
 drivers/media/pci/facetimehd/fthd_isp.h     |  770 ++++++++++
 drivers/media/pci/facetimehd/fthd_reg.h     |  183 +++
 drivers/media/pci/facetimehd/fthd_ringbuf.c |  133 ++
 drivers/media/pci/facetimehd/fthd_ringbuf.h |   45 +
 drivers/media/pci/facetimehd/fthd_v4l2.c    |  759 ++++++++++
 drivers/media/pci/facetimehd/fthd_v4l2.h    |   34 +
 24 files changed, 6124 insertions(+)
 create mode 100644 drivers/media/pci/facetimehd/FIRMWARE
 create mode 100644 drivers/media/pci/facetimehd/Kconfig
 create mode 100644 drivers/media/pci/facetimehd/Makefile
 create mode 100755 drivers/media/pci/facetimehd/firmware.sh
 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

-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/5] facetimehd: copy code from patjak/facetimehd
  2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
@ 2026-08-13 17:35 ` Jack Flusche
  2026-08-13 17:35 ` [PATCH 2/5] facetimehd: add Kconfig and Makefile Jack Flusche
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

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


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/5] facetimehd: add Kconfig and Makefile
  2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
  2026-08-13 17:35 ` [PATCH 1/5] facetimehd: copy code from patjak/facetimehd Jack Flusche
@ 2026-08-13 17:35 ` Jack Flusche
  2026-08-13 17:35 ` [PATCH 3/5] facetimehd: fix compile-time issues and formatting Jack Flusche
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

Added build files for the FacetimeHD driver

Signed-off-by: Jack Flusche <jackflusche@gmail.com>
---
 drivers/media/pci/Kconfig             |  1 +
 drivers/media/pci/Makefile            |  1 +
 drivers/media/pci/facetimehd/Kconfig  | 12 ++++++++++++
 drivers/media/pci/facetimehd/Makefile |  2 ++
 4 files changed, 16 insertions(+)
 create mode 100644 drivers/media/pci/facetimehd/Kconfig
 create mode 100644 drivers/media/pci/facetimehd/Makefile

diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index bfdb200f85a3..66f3d8b0ad6f 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -20,6 +20,7 @@ source "drivers/media/pci/tw5864/Kconfig"
 source "drivers/media/pci/tw68/Kconfig"
 source "drivers/media/pci/tw686x/Kconfig"
 source "drivers/media/pci/zoran/Kconfig"
+source "drivers/media/pci/facetimehd/Kconfig"
 
 endif
 
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index c4508b6723a9..666099f23abd 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -39,3 +39,4 @@ obj-$(CONFIG_VIDEO_TW5864) += tw5864/
 obj-$(CONFIG_VIDEO_TW686X) += tw686x/
 obj-$(CONFIG_VIDEO_TW68) += tw68/
 obj-$(CONFIG_VIDEO_ZORAN) += zoran/
+obj-$(CONFIG_VIDEO_FACETIMEHD) += facetimehd/
diff --git a/drivers/media/pci/facetimehd/Kconfig b/drivers/media/pci/facetimehd/Kconfig
new file mode 100644
index 000000000000..34d3ba8999f4
--- /dev/null
+++ b/drivers/media/pci/facetimehd/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# "Broadcom Facetime HD camera driver
+#
+config VIDEO_FACETIMEHD
+	tristate "Broadcom Facetime HD camera"
+	select VIDEOBUF2_CORE
+	select VIDEOBUF2_DMA_SG
+	select VIDEO_DEV
+	help
+	  This adds support for the Apple Facetime HD camera using the Broadcom
+	  PCIe controller (14e4:1570) found on Macbooks from around 2013-2017.
diff --git a/drivers/media/pci/facetimehd/Makefile b/drivers/media/pci/facetimehd/Makefile
new file mode 100644
index 000000000000..2dc42bbc8a8f
--- /dev/null
+++ b/drivers/media/pci/facetimehd/Makefile
@@ -0,0 +1,2 @@
+facetimehd-objs := fthd_ddr.o fthd_hw.o fthd_drv.o fthd_ringbuf.o fthd_isp.o fthd_v4l2.o fthd_buffer.o fthd_debugfs.o
+obj-$(CONFIG_VIDEO_FACETIMEHD) += facetimehd.o
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 3/5] facetimehd: fix compile-time issues and formatting
  2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
  2026-08-13 17:35 ` [PATCH 1/5] facetimehd: copy code from patjak/facetimehd Jack Flusche
  2026-08-13 17:35 ` [PATCH 2/5] facetimehd: add Kconfig and Makefile Jack Flusche
@ 2026-08-13 17:35 ` Jack Flusche
  2026-08-13 17:35 ` [PATCH 4/5] facetimehd: add firmware extraction script from patjak/facetimehd-firmware Jack Flusche
  2026-08-13 17:35 ` [PATCH 5/5] facetimehd: add entry to MAINTAINERS Jack Flusche
  4 siblings, 0 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

Fix errors and warnings from clangd and checkpatch.pl to meet kernel
code standards in the FacetimeHD driver

Signed-off-by: Jack Flusche <jackflusche@gmail.com>
---
 drivers/media/pci/facetimehd/fthd_buffer.c  |  33 ++---
 drivers/media/pci/facetimehd/fthd_buffer.h  |  14 +-
 drivers/media/pci/facetimehd/fthd_ddr.c     |  29 ++--
 drivers/media/pci/facetimehd/fthd_ddr.h     |   5 +-
 drivers/media/pci/facetimehd/fthd_debugfs.c |  14 +-
 drivers/media/pci/facetimehd/fthd_debugfs.h |   3 +-
 drivers/media/pci/facetimehd/fthd_drv.c     |  40 +++---
 drivers/media/pci/facetimehd/fthd_drv.h     |  16 +--
 drivers/media/pci/facetimehd/fthd_hw.c      |  62 +++++----
 drivers/media/pci/facetimehd/fthd_hw.h      |   4 +-
 drivers/media/pci/facetimehd/fthd_isp.c     |  75 ++++++-----
 drivers/media/pci/facetimehd/fthd_isp.h     |  19 +--
 drivers/media/pci/facetimehd/fthd_reg.h     |   3 +-
 drivers/media/pci/facetimehd/fthd_ringbuf.c |  10 +-
 drivers/media/pci/facetimehd/fthd_ringbuf.h |  11 +-
 drivers/media/pci/facetimehd/fthd_v4l2.c    | 141 ++++++++------------
 drivers/media/pci/facetimehd/fthd_v4l2.h    |   4 +-
 17 files changed, 227 insertions(+), 256 deletions(-)

diff --git a/drivers/media/pci/facetimehd/fthd_buffer.c b/drivers/media/pci/facetimehd/fthd_buffer.c
index fae443c1f380..4955e2bb028e 100644
--- a/drivers/media/pci/facetimehd/fthd_buffer.c
+++ b/drivers/media/pci/facetimehd/fthd_buffer.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -24,9 +23,9 @@ struct buf_ctx {
 
 static int iommu_allocator_init(struct fthd_private *dev_priv)
 {
-        dev_priv->iommu = kzalloc(sizeof(struct resource), GFP_KERNEL);
+	dev_priv->iommu = kzalloc_obj(struct resource, GFP_KERNEL);
 	if (!dev_priv->iommu)
-	    return -ENOMEM;
+		return -ENOMEM;
 
 	dev_priv->iommu->start = 0;
 	dev_priv->iommu->end = 4095;
@@ -41,17 +40,17 @@ struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct s
 	int ret, i, pos;
 	int total_len = 0, dma_length;
 	dma_addr_t dma_addr;
-	
-	for(i = 0; i < sgtable->nents; i++)
+
+	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);
+
+	obj = kzalloc_obj(struct iommu_obj, GFP_KERNEL);
 	if (!obj)
 		return NULL;
 
@@ -60,7 +59,7 @@ struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct s
 				1, NULL, NULL);
 	if (ret) {
 		dev_err(&dev_priv->pdev->dev,
-			"Failed to allocate resource (size: %d, start: %Ld, end: %Ld)\n",
+			"Failed to allocate resource (size: %d, start: %lld, end: %lld)\n",
 			total_len, root->start, root->end);
 		kfree(obj);
 		obj = NULL;
@@ -71,14 +70,14 @@ struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct s
 	obj->size = total_len;
 
 	pos = 0x9000 + obj->offset * 4;
-	for(i = 0; i < sgtable->nents; i++) {
+	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) {
+
+		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;
@@ -92,12 +91,13 @@ struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct s
 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++)
+
+	for (i = obj->offset; i < obj->offset + obj->size; i++)
 		FTHD_S2_REG_WRITE(0, 0x9000 + i * 4);
 
 	release_resource(&obj->base);
@@ -113,7 +113,8 @@ static void iommu_allocator_destroy(struct fthd_private *dev_priv)
 int fthd_buffer_init(struct fthd_private *dev_priv)
 {
 	int i;
-	for(i = 0; i < 0x1000; i++)
+
+	for (i = 0; i < 0x1000; i++)
 		FTHD_S2_REG_WRITE(0, 0x9000 + i * 4);
 
 	return iommu_allocator_init(dev_priv);
diff --git a/drivers/media/pci/facetimehd/fthd_buffer.h b/drivers/media/pci/facetimehd/fthd_buffer.h
index 2e76ded90215..c6d390288783 100644
--- a/drivers/media/pci/facetimehd/fthd_buffer.h
+++ b/drivers/media/pci/facetimehd/fthd_buffer.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -10,7 +9,8 @@
 #define FTHD_BUFFER_H
 
 #include <linux/scatterlist.h>
-#include "fthd_buffer.h"
+
+struct fthd_private;
 
 enum fthd_buffer_state {
 	BUF_FREE,
@@ -29,14 +29,14 @@ struct dma_descriptor {
 	u32 count;
 	u32 pool;
 	u64 tag;
-} __attribute__((packed));
+} __packed;
 
 struct dma_descriptor_list {
 	u32 field0;
-    	u32 count;
+	u32 count;
 	struct dma_descriptor desc[4];
 	char unknown[216];
-} __attribute__((packed));
+} __packed;
 
 struct iommu_obj {
 	struct resource base;
@@ -68,6 +68,6 @@ 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 struct iommu_obj *iommu_allocate_sgtable(struct fthd_private *dev_priv, struct sg_table *sgtable);
 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
index df21537680db..3bacdf0125c4 100644
--- a/drivers/media/pci/facetimehd/fthd_ddr.c
+++ b/drivers/media/pci/facetimehd/fthd_ddr.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
  * FacetimeHD camera driver
  *
@@ -17,13 +18,7 @@
  *
  */
 
-#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"
@@ -302,12 +297,11 @@ static int fthd_ddr_calibrate_one_re_fifo(struct fthd_private *dev_priv,
 		*rden_byte1 = bl_start[1];
 	}
 
-	if (*rden_byte0 > 63) {
+	if (*rden_byte0 > 63)
 		*rden_byte0 = 63;
-	}
-	if (*rden_byte1 > 63) {
+
+	if (*rden_byte1 > 63)
 		*rden_byte1 = 63;
-	}
 
 	return 0;
 }
@@ -429,13 +423,14 @@ static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv,
 	u32 pass_end[16]; // u32 var_f0[16];
 	int fail_sum, i, j, bit;
 	s32 setting;
-	printk(KERN_CONT "\n");
+
+	dev_info(&dev_priv->pdev->dev, "Calibrating RD DQS: ");
 
 	for (bit = 0; bit < 16; bit++) {
 		pass_start[bit] = 64;
 		pass_end[bit] = 64;
 
-		printk(KERN_CONT "%.2d: ", bit);
+		pr_cont("%.2d: ", bit);
 
 		/* Start looking for start of pass */
 		for (i = 0; i < 63; i++) {
@@ -446,9 +441,9 @@ static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv,
 				fail_sum += fails[i + j] & (1 << bit);
 
 			if (fail_sum) {
-				printk(KERN_CONT ".");
+				pr_cont(".");
 			} else {
-				printk(KERN_CONT "O");
+				pr_cont("O");
 
 				pass_start[bit] = i;
 				break;
@@ -461,9 +456,9 @@ static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv,
 				if (pass_end[bit] == 64)
 					pass_end[bit] = i;
 
-				printk(KERN_CONT ".");
+				pr_cont(".");
 			} else {
-				printk(KERN_CONT "O");
+				pr_cont("O");
 			}
 		}
 
@@ -478,7 +473,7 @@ static int fthd_ddr_calibrate_rd_dqs(struct fthd_private *dev_priv,
 			setting = 63;
 		settings[bit] = setting;
 
-		printk(KERN_CONT " : start=%d end=%d len=%d new=%d\n", pass_start[bit],
+		pr_cont(" : start=%d end=%d len=%d new=%d\n", pass_start[bit],
 		       pass_end[bit], pass_len[bit], settings[bit]);
 	}
 
diff --git a/drivers/media/pci/facetimehd/fthd_ddr.h b/drivers/media/pci/facetimehd/fthd_ddr.h
index 162e7c343ab7..c31acd5b3bda 100644
--- a/drivers/media/pci/facetimehd/fthd_ddr.h
+++ b/drivers/media/pci/facetimehd/fthd_ddr.h
@@ -1,5 +1,6 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Broadcom PCIe 1570 webcam driver
+ * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
  *
@@ -20,6 +21,8 @@
 #ifndef _FTHD_DDR_H
 #define _FTHD_DDR_H
 
+#include "fthd_drv.h"
+
 #define MEM_VERIFY_BASE		0x0 /* 0x1000000 */
 #define MEM_VERIFY_NUM		128
 #define MEM_VERIFY_NUM_FULL	(1 * 1024 * 1024)
diff --git a/drivers/media/pci/facetimehd/fthd_debugfs.c b/drivers/media/pci/facetimehd/fthd_debugfs.c
index 50ab5a75f14c..4807864a9f49 100644
--- a/drivers/media/pci/facetimehd/fthd_debugfs.c
+++ b/drivers/media/pci/facetimehd/fthd_debugfs.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -93,7 +92,7 @@ static int seq_channel_read(struct seq_file *seq, struct fthd_private *dev_priv,
 	u32 entry;
 
 	spin_lock_irq(&chan->lock);
-	for( i = 0; i < chan->size; i++) {
+	for (i = 0; i < chan->size; i++) {
 		if (chan->ringbuf.idx == i)
 			pos = '*';
 		else
@@ -113,6 +112,7 @@ 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);
 }
 
@@ -120,6 +120,7 @@ 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);
 }
 
@@ -127,6 +128,7 @@ 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);
 }
 
@@ -134,6 +136,7 @@ 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);
 }
 
@@ -141,6 +144,7 @@ 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);
 }
 
@@ -148,6 +152,7 @@ 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);
 }
 
@@ -155,6 +160,7 @@ 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);
 }
 
@@ -187,7 +193,7 @@ int fthd_debugfs_init(struct fthd_private *dev_priv)
 	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);
+	debugfs_create_file("debug", 0600, d, dev_priv, &fops_debug);
 	dev_priv->debugfs = top;
 	return 0;
 }
diff --git a/drivers/media/pci/facetimehd/fthd_debugfs.h b/drivers/media/pci/facetimehd/fthd_debugfs.h
index d3f8b74239e4..f665117abdc5 100644
--- a/drivers/media/pci/facetimehd/fthd_debugfs.h
+++ b/drivers/media/pci/facetimehd/fthd_debugfs.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
diff --git a/drivers/media/pci/facetimehd/fthd_drv.c b/drivers/media/pci/facetimehd/fthd_drv.c
index c8d715122e5f..f991d08a8a31 100644
--- a/drivers/media/pci/facetimehd/fthd_drv.c
+++ b/drivers/media/pci/facetimehd/fthd_drv.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -11,10 +10,6 @@
 #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>
@@ -97,7 +92,7 @@ static void sharedmalloc_handler(struct fthd_private *dev_priv,
 
 	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;
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~3;
 
 	if (address) {
 		pr_debug("Firmware wants to free memory at %08x\n", address);
@@ -106,7 +101,7 @@ static void sharedmalloc_handler(struct fthd_private *dev_priv,
 
 		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);
+			pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 	} else {
 		if (!request_size)
 			return;
@@ -115,12 +110,12 @@ static void sharedmalloc_handler(struct fthd_private *dev_priv,
 			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 >> 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);
+			pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 
 	}
 
@@ -136,7 +131,7 @@ static void terminal_handler(struct fthd_private *dev_priv,
 
 	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;
+	address = FTHD_S2_MEM_READ(entry + FTHD_RINGBUF_ADDRESS_FLAGS) & ~3;
 
 	if (!address || !request_size)
 		return;
@@ -153,6 +148,7 @@ static void buf_t2h_handler(struct fthd_private *dev_priv,
 {
 	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);
@@ -165,7 +161,7 @@ static void buf_t2h_handler(struct fthd_private *dev_priv,
 	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);
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 
 }
 
@@ -174,8 +170,9 @@ static void io_t2h_handler(struct fthd_private *dev_priv,
 				 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);
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 
 }
 
@@ -202,7 +199,7 @@ static void fthd_handle_irq(struct fthd_private *dev_priv, struct fw_channel *ch
 		return;
 	}
 
-	while((entry = fthd_channel_ringbuf_receive(dev_priv, chan)) != (u32)-1) {
+	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);
@@ -210,7 +207,7 @@ static void fthd_handle_irq(struct fthd_private *dev_priv, struct fw_channel *ch
 			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);
+				pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 		} else if (chan == dev_priv->channel_buf_t2h) {
 			buf_t2h_handler(dev_priv, chan, entry);
 		} else if (chan == dev_priv->channel_io_t2h) {
@@ -232,7 +229,7 @@ static void fthd_irq_work(struct work_struct *work)
 	u32 pending;
 	int i = 0;
 
-	while(i++ < 500) {
+	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);
@@ -246,7 +243,7 @@ static void fthd_irq_work(struct work_struct *work)
 		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++) {
+		for (i = 0; i < dev_priv->num_channels; i++) {
 			chan = dev_priv->channels[i];
 
 
@@ -396,11 +393,7 @@ static int fthd_pci_init(struct fthd_private *dev_priv)
 		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);
@@ -441,7 +434,8 @@ static int fthd_firmware_start(struct fthd_private *dev_priv)
 
 	/* 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. */
+	 * to a default size.
+	 */
 	fthd_isp_cmd_channel_camera_config(dev_priv);
 
 	return fthd_isp_cmd_set_loadfile(dev_priv);
@@ -457,7 +451,7 @@ static int fthd_pci_probe(struct pci_dev *pdev,
 	dev_info(&pdev->dev, "Found FaceTime HD camera with device id: %x\n",
 		 pdev->device);
 
-	dev_priv = kzalloc(sizeof(struct fthd_private), GFP_KERNEL);
+	dev_priv = kzalloc_obj(struct fthd_private, GFP_KERNEL);
 	if (!dev_priv) {
 		dev_err(&pdev->dev, "Failed to allocate memory\n");
 		return -ENOMEM;
diff --git a/drivers/media/pci/facetimehd/fthd_drv.h b/drivers/media/pci/facetimehd/fthd_drv.h
index 439143fd337d..bed989a40617 100644
--- a/drivers/media/pci/facetimehd/fthd_drv.h
+++ b/drivers/media/pci/facetimehd/fthd_drv.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -14,7 +13,6 @@
 #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>
@@ -30,9 +28,9 @@
 #define FTHD_BUFFERS 4
 
 enum FW_CHAN_TYPE {
-	FW_CHAN_TYPE_OUT=0,
-	FW_CHAN_TYPE_IN=1,
-	FW_CHAN_TYPE_UNI_IN=2,
+	FW_CHAN_TYPE_OUT = 0,
+	FW_CHAN_TYPE_IN = 1,
+	FW_CHAN_TYPE_UNI_IN = 2,
 };
 
 struct fw_channel {
@@ -105,7 +103,8 @@ struct fthd_private {
 	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. */
+	 * (MacBook8,1, sensor 1675) reports 848x588. 0 until detected.
+	 */
 	unsigned int sensor_width;
 	unsigned int sensor_height;
 
@@ -114,9 +113,6 @@ struct fthd_private {
 	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;
diff --git a/drivers/media/pci/facetimehd/fthd_hw.c b/drivers/media/pci/facetimehd/fthd_hw.c
index 8180072922d4..2c04392f3dfe 100644
--- a/drivers/media/pci/facetimehd/fthd_hw.c
+++ b/drivers/media/pci/facetimehd/fthd_hw.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -12,6 +11,7 @@
 #include "fthd_hw.h"
 #include "fthd_ddr.h"
 #include "fthd_isp.h"
+#include "fthd_reg.h"
 
 static int fthd_hw_s2_pll_reset(struct fthd_private *dev_priv)
 {
@@ -20,7 +20,7 @@ static int fthd_hw_s2_pll_reset(struct fthd_private *dev_priv)
 	FTHD_S2_REG_WRITE(0xbcbc1500, S2_PLL_CTRL_100);
 	FTHD_S2_REG_WRITE(0x0, S2_PLL_CTRL_14);
 
-	udelay(10000);
+	mdelay(10);
 
 	FTHD_S2_REG_WRITE(0x3, S2_PLL_CTRL_14);
 
@@ -142,14 +142,14 @@ static int fthd_hw_s2_pll_init(struct fthd_private *dev_priv, u32 ddr_speed)
 		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));
 	}
 
+	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);
+	mdelay(10);
 
 	reg = FTHD_S2_REG_READ(S2_PLL_STATUS_A8);
 	if (reg & S2_PLL_BYPASS)
@@ -182,7 +182,7 @@ static int fthd_hw_ddr_phy_soft_reset(struct fthd_private *dev_priv)
 
 	FTHD_S2_REG_WRITE(0xfffff, S2_PLL_CTRL_9C);
 
-	udelay(10000);
+	mdelay(10);
 
 	FTHD_S2_REG_WRITE(0xffbff, S2_PLL_CTRL_9C);
 
@@ -323,7 +323,7 @@ static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv)
 		return -EIO;
 	}
 
-	udelay(10000);
+	mdelay(10);
 
 	/* WL */
 	FTHD_S2_REG_WRITE(0x0c10, S2_DDR40_PHY_PLL_DIV);
@@ -397,10 +397,10 @@ static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv)
 		dev_err(&dev_priv->pdev->dev,
 			"Timeout waiting for STRAP valid\n");
 		return -ENODEV;
-	} else {
-		dev_info(&dev_priv->pdev->dev, "STRAP valid\n");
 	}
 
+	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,
@@ -565,7 +565,7 @@ static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv)
 	udelay(500);
 
 	FTHD_S2_REG_WRITE(0, S2_DDR_2004);
-	udelay(10000);
+	mdelay(10);
 
 	FTHD_S2_REG_WRITE(0xab0a, S2_DDR_2014);
 
@@ -574,7 +574,7 @@ static int fthd_hw_s2_init_ddr_controller_soc(struct fthd_private *dev_priv)
 	if (ret != 0)
 		return -EBUSY;
 
-	udelay(10000);
+	mdelay(10);
 
 	FTHD_S2_REG_WRITE(0, S2_3204);
 
@@ -652,35 +652,34 @@ int fthd_hw_init(struct fthd_private *dev_priv)
 
 	ret = fthd_hw_s2_init_pcie_link(dev_priv);
 	if (ret)
-		goto out;
+		return ret;
 
 	fthd_hw_s2_preinit_ddr_controller_soc(dev_priv);
 	fthd_hw_s2_init_ddr_controller_soc(dev_priv);
 
-/*
-	dev_info(&dev_priv->pdev->dev,
+	dev_dbg(&dev_priv->pdev->dev,
 		 "Dumping DDR PHY reg map before shmoo\n");
 
-	for (i = 0; i < DDR_PHY_NUM_REGS; i++) {
+	for (int i = 0; i < ARRAY_SIZE(fthd_ddr_phy_reg_map); i++) {
 		if (!(i % 3) && i >  0)
-			printk("\n");
+			dev_dbg(&dev_priv->pdev->dev, "\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);
+		int val = FTHD_S2_REG_READ(fthd_ddr_phy_reg_map[i]);
+
+		pr_cont("0x%.3x = 0x%.8x\t",
+			 fthd_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.
-		 */
+		dev_warn(&dev_priv->pdev->dev,
+			"Full memory verification failed, calibrating. (%d)\n", ret);
 
-		/* fthd_ddr_calibrate(dev_priv); */
+		ret = fthd_ddr_calibrate(dev_priv);
+		if (ret) {
+			dev_err(&dev_priv->pdev->dev,
+				"Calibration failed! (%d)\n", ret);
+		}
 	} else {
 		dev_info(&dev_priv->pdev->dev,
 			 "Full memory verification succeeded! (%d)\n", ret);
@@ -696,17 +695,16 @@ int fthd_hw_init(struct fthd_private *dev_priv)
 
 	ret = isp_init(dev_priv);
 	if (ret)
-	    goto out;
+		return ret;
 
 	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
index 58d802488f67..ea91847462e0 100644
--- a/drivers/media/pci/facetimehd/fthd_hw.h
+++ b/drivers/media/pci/facetimehd/fthd_hw.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -11,6 +10,7 @@
 #define _FTHD_HW_H
 
 #include <linux/pci.h>
+#include "fthd_drv.h"
 
 /* Used after most PCI Link IO writes */
 static inline void fthd_hw_pci_post(struct fthd_private *dev_priv)
diff --git a/drivers/media/pci/facetimehd/fthd_isp.c b/drivers/media/pci/facetimehd/fthd_isp.c
index bef1cd4b03c3..d721925f3664 100644
--- a/drivers/media/pci/facetimehd/fthd_isp.c
+++ b/drivers/media/pci/facetimehd/fthd_isp.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -22,9 +21,9 @@ 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);
+	dev_priv->mem = kzalloc_obj(struct resource, GFP_KERNEL);
 	if (!dev_priv->mem)
-	    return -ENOMEM;
+		return -ENOMEM;
 
 	dev_priv->mem->start = root->start;
 	dev_priv->mem->end = root->end;
@@ -48,7 +47,7 @@ struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv,
 	struct resource *root = dev_priv->mem;
 	int ret;
 
-	obj = kzalloc(sizeof(struct isp_mem_obj), GFP_KERNEL);
+	obj = kzalloc_obj(struct isp_mem_obj, GFP_KERNEL);
 	if (!obj)
 		return NULL;
 
@@ -58,7 +57,7 @@ struct isp_mem_obj *isp_mem_create(struct fthd_private *dev_priv,
 				PAGE_SIZE, NULL, NULL);
 	if (ret) {
 		dev_err(&dev_priv->pdev->dev,
-			"Failed to allocate resource (size: %Ld, start: %Ld, end: %Ld)\n",
+			"Failed to allocate resource (size: %lld, start: %lld, end: %lld)\n",
 			size, root->start, root->end);
 		kfree(obj);
 		obj = NULL;
@@ -93,7 +92,7 @@ static int isp_acpi_set_power(struct fthd_private *dev_priv, int power)
 
 
 	handle = ACPI_HANDLE(&dev_priv->pdev->dev);
-	if(!handle) {
+	if (!handle) {
 		dev_err(&dev_priv->pdev->dev,
 			"Failed to get S2 CMPE ACPI handle\n");
 		ret = -ENODEV;
@@ -118,7 +117,7 @@ static int isp_acpi_set_power(struct fthd_private *dev_priv, int power)
 
 	if (result->type != ACPI_TYPE_INTEGER || result->integer.value != 0) {
 		dev_err(&dev_priv->pdev->dev,
-			"Invalid ACPI response (len: %Ld)\n", buffer.length);
+			"Invalid ACPI response (len: %lld)\n", buffer.length);
 		ret = -EINVAL;
 	}
 
@@ -180,7 +179,8 @@ static void isp_free_channel_info(struct fthd_private *priv)
 {
 	struct fw_channel *chan;
 	int i;
-	for(i = 0; i < priv->num_channels; i++) {
+
+	for (i = 0; i < priv->num_channels; i++) {
 		chan = priv->channels[i];
 		if (!chan)
 			continue;
@@ -196,7 +196,8 @@ static void isp_free_channel_info(struct fthd_private *priv)
 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++) {
+
+	for (i = 0; i < priv->num_channels; i++) {
 		if (!strcasecmp(priv->channels[i]->name, name))
 			return priv->channels[i];
 	}
@@ -212,16 +213,16 @@ static int isp_fill_channel_info(struct fthd_private *dev_priv, int offset, int
 	if (!num_channels)
 		return -EINVAL;
 
-	dev_priv->channels = kzalloc(num_channels * sizeof(struct fw_channel *), GFP_KERNEL);
+	dev_priv->channels = kzalloc_objs(struct fw_channel *, num_channels, GFP_KERNEL);
 	if (!dev_priv->channels)
 		goto out;
 
 	dev_priv->num_channels = num_channels;
 
-	for(i = 0; i < num_channels; i++) {
+	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);
+		chan = kzalloc_obj(struct fw_channel, GFP_KERNEL);
 		if (!chan)
 			goto out;
 
@@ -274,11 +275,11 @@ static int fthd_isp_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds comman
 
 	memset(&cmd, 0, sizeof(cmd));
 
-	if (response_len) {
+	if (response_len)
 		len = max(request_len, *response_len);
-	} else {
+	else
 		len = request_len;
-	}
+
 	len += sizeof(struct isp_cmd_hdr);
 
 	pr_debug("sending cmd %d to firmware\n", command);
@@ -311,7 +312,7 @@ static int fthd_isp_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds comman
 		goto out;
 	}
 
-        ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_io, entry, 2000);
+	ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_io, entry, 2000);
 	if (ret) {
 		if (response_len)
 			*response_len = 0;
@@ -323,8 +324,9 @@ static int fthd_isp_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds comman
 	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? */
+	/* 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);
@@ -349,11 +351,11 @@ int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command
 
 	memset(&cmd, 0, sizeof(cmd));
 
-	if (response_len) {
+	if (response_len)
 		len = max(request_len, *response_len);
-	} else {
+	else
 		len = request_len;
-	}
+
 	len += sizeof(struct isp_cmd_hdr);
 
 	pr_debug("sending debug cmd %d to firmware\n", command);
@@ -380,7 +382,7 @@ int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command
 		goto out;
 	}
 
-        ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_debug, entry, 20000);
+	ret = fthd_channel_wait_ready(dev_priv, dev_priv->channel_debug, entry, 20000);
 	if (ret) {
 		if (response_len)
 			*response_len = 0;
@@ -392,8 +394,9 @@ int fthd_isp_debug_cmd(struct fthd_private *dev_priv, enum fthd_isp_cmds command
 	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? */
+	/* 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);
@@ -418,6 +421,7 @@ int fthd_isp_cmd_start(struct fthd_private *dev_priv)
 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;
@@ -530,7 +534,7 @@ int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv)
 
 	memset(&cmd, 0, sizeof(cmd));
 
-	switch(dev_priv->sensor_id1) {
+	switch (dev_priv->sensor_id1) {
 	case 0x164:
 		filename = "facetimehd/8221_01XX.dat";
 		break;
@@ -547,7 +551,7 @@ int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv)
 			break;
 		}
 
-		switch(dev_priv->sensor_id0) {
+		switch (dev_priv->sensor_id0) {
 		case 4:
 			filename = "facetimehd/1874_01XX.dat";
 			break;
@@ -557,7 +561,7 @@ int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv)
 		}
 		break;
 	case 0x9774:
-		switch(dev_priv->sensor_id0) {
+		switch (dev_priv->sensor_id0) {
 		case 4:
 			filename = "facetimehd/1674_01XX.dat";
 			break;
@@ -582,8 +586,10 @@ int fthd_isp_cmd_set_loadfile(struct fthd_private *dev_priv)
 
 	/* 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)
+	if (ret) {
+		pr_info("Firmware set file %s not found, no calibration possible\n", filename);
 		return 0;
+	}
 
 	/* Firmware memory is preallocated at init time */
 	BUG_ON(dev_priv->set_file);
@@ -650,10 +656,11 @@ 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++) {
+	for (i = 0; i < dev_priv->sensor_count; i++) {
 		cmd.channel = i;
 
 		len = sizeof(cmd);
@@ -667,7 +674,8 @@ int fthd_isp_cmd_channel_camera_config(struct fthd_private *dev_priv)
 		 * 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. */
+		 * 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);
@@ -1148,7 +1156,8 @@ int fthd_start_channel(struct fthd_private *dev_priv, int channel)
 	/* 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. */
+	 * sensor interface throw SIF errors. Use the negotiated format size.
+	 */
 	x1 = 0;
 	x2 = dev_priv->fmt.fmt.width;
 
@@ -1157,7 +1166,7 @@ int fthd_start_channel(struct fthd_private *dev_priv, int channel)
 	if (ret)
 		return ret;
 
-	switch(dev_priv->fmt.fmt.pixelformat) {
+	switch (dev_priv->fmt.fmt.pixelformat) {
 	case V4L2_PIX_FMT_YUYV:
 		pixelformat = 1;
 		break;
diff --git a/drivers/media/pci/facetimehd/fthd_isp.h b/drivers/media/pci/facetimehd/fthd_isp.h
index b12108f65193..29c744e5cb18 100644
--- a/drivers/media/pci/facetimehd/fthd_isp.h
+++ b/drivers/media/pci/facetimehd/fthd_isp.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
@@ -10,6 +9,10 @@
 #ifndef _ISP_H
 #define _ISP_H
 
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include "fthd_drv.h"
+
 /* ISP memory types */
 #define FTHD_MEM_FIRMWARE	1
 #define FTHD_MEM_HEAP		2
@@ -438,7 +441,7 @@ enum fthd_isp_cmds {
 };
 
 enum isp_debug_cmds {
-	CISP_CMD_DEBUG_BANNER=0,
+	CISP_CMD_DEBUG_BANNER = 0,
 	CISP_CMD_DEBUG_NOP1,
 	CISP_CMD_DEBUG_NOP2,
 	CISP_CMD_DEBUG_PS,
@@ -494,11 +497,11 @@ struct isp_cmd_hdr {
 	u32 unknown0;
 	u16 opcode;
 	u16 status;
-} __attribute__((packed));
+} __packed;
 
 struct isp_cmd_print_enable {
 	u32 enable;
-} __attribute__((packed));
+} __packed;
 
 struct isp_cmd_config {
 	u32 field0;
@@ -509,13 +512,13 @@ struct isp_cmd_config {
 	u32 field14;
 	u32 field18;
 	u32 field1c;
-} __attribute__((packed));
+} __packed;
 
 struct isp_cmd_set_loadfile {
 	u32 unknown;
 	u32 addr;
 	u32 length;
-} __attribute__((packed));
+} __packed;
 
 struct isp_cmd_channel_info {
 	u32 field_0;
@@ -534,7 +537,7 @@ struct isp_cmd_channel_info {
 	u8 unknown2[40];
 	u8 sensor_serial_number[8];
 	u8 camera_module_serial_number[18];
-} __attribute__((packed));
+} __packed;
 
 struct isp_cmd_channel_camera_config {
 	u32 unknown;
diff --git a/drivers/media/pci/facetimehd/fthd_reg.h b/drivers/media/pci/facetimehd/fthd_reg.h
index 27da24a4a1d3..6937e61f3e76 100644
--- a/drivers/media/pci/facetimehd/fthd_reg.h
+++ b/drivers/media/pci/facetimehd/fthd_reg.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
diff --git a/drivers/media/pci/facetimehd/fthd_ringbuf.c b/drivers/media/pci/facetimehd/fthd_ringbuf.c
index 7b099ae56379..2dc6db323d3b 100644
--- a/drivers/media/pci/facetimehd/fthd_ringbuf.c
+++ b/drivers/media/pci/facetimehd/fthd_ringbuf.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -15,7 +14,6 @@
 #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)
@@ -29,7 +27,7 @@ void fthd_channel_ringbuf_dump(struct fthd_private *dev_priv, struct fw_channel
 	char pos;
 	int i;
 
-	for( i = 0; i < chan->size; i++) {
+	for (i = 0; i < chan->size; i++) {
 		if (chan->ringbuf.idx == i)
 			pos = '*';
 		else
@@ -55,7 +53,7 @@ void fthd_channel_ringbuf_init(struct fthd_private *dev_priv, struct fw_channel
 			 chan->name, chan->offset, chan->size);
 
 		spin_lock_irq(&chan->lock);
-		for(i = 0; i < chan->size; i++) {
+		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);
@@ -86,7 +84,7 @@ int fthd_channel_ringbuf_send(struct fthd_private *dev_priv, struct fw_channel *
 
 	FTHD_S2_MEM_WRITE(request_size, entry + FTHD_RINGBUF_REQUEST_SIZE);
 	FTHD_S2_MEM_WRITE(response_size, entry + FTHD_RINGBUF_RESPONSE_SIZE);
-	wmb();
+	wmb(); /* Force write to request size and response size buffers */
 	FTHD_S2_MEM_WRITE(data_offset | (chan->type == 0 ? 0 : 1),
 			  entry + FTHD_RINGBUF_ADDRESS_FLAGS);
 	spin_unlock_irq(&chan->lock);
diff --git a/drivers/media/pci/facetimehd/fthd_ringbuf.h b/drivers/media/pci/facetimehd/fthd_ringbuf.h
index a59fb5e0e21c..2adfe4f2d1ce 100644
--- a/drivers/media/pci/facetimehd/fthd_ringbuf.h
+++ b/drivers/media/pci/facetimehd/fthd_ringbuf.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -10,6 +9,8 @@
 #ifndef _FTHD_RINGBUF_H
 #define _FTHD_RINGBUF_H
 
+#include <linux/types.h>
+
 #define FTHD_RINGBUF_ENTRY_SIZE 64
 
 #define FTHD_RINGBUF_ADDRESS_FLAGS 0
@@ -17,8 +18,8 @@
 #define FTHD_RINGBUF_RESPONSE_SIZE 8
 
 enum ringbuf_type_t {
-	RINGBUF_TYPE_H2T=0,
-	RINGBUF_TYPE_T2H=1,
+	RINGBUF_TYPE_H2T = 0,
+	RINGBUF_TYPE_T2H = 1,
 	RINGBUF_TYPE_UNIDIRECTIONAL,
 };
 
@@ -31,7 +32,7 @@ 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 u32 fthd_channel_ringbuf_get_entry(struct fthd_private *dev_priv, struct fw_channel *chan);
 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);
 
diff --git a/drivers/media/pci/facetimehd/fthd_v4l2.c b/drivers/media/pci/facetimehd/fthd_v4l2.c
index 11414473f7f0..46c70c76cf7d 100644
--- a/drivers/media/pci/facetimehd/fthd_v4l2.c
+++ b/drivers/media/pci/facetimehd/fthd_v4l2.c
@@ -1,6 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-only
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -12,7 +11,6 @@
 #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>
@@ -26,34 +24,23 @@
 #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. */
+ * 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
-) {
+static int fthd_buffer_queue_setup
+(
+	struct vb2_queue *vq,
+	unsigned int *nbuffers,
+	unsigned int *nplanes,
+	unsigned int sizes[],
+	struct device *alloc_devs[]
+)
+{
 
 	struct fthd_private *dev_priv = vb2_get_drv_priv(vq);
 	struct v4l2_pix_format *cur_fmt = &dev_priv->fmt.fmt;
@@ -70,11 +57,7 @@ static int fthd_buffer_queue_setup(
 	/* 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];
 	}
 
@@ -95,7 +78,7 @@ static void fthd_buffer_cleanup(struct vb2_buffer *vb)
 	int i;
 
 	pr_debug("%p\n", vb);
-	for(i = 0; i < FTHD_BUFFERS; i++) {
+	for (i = 0; i < FTHD_BUFFERS; i++) {
 		if (dev_priv->h2t_bufs[i].vb == vb) {
 			ctx = dev_priv->h2t_bufs + i;
 			break;
@@ -107,7 +90,7 @@ static void fthd_buffer_cleanup(struct vb2_buffer *vb)
 	ctx->state = BUF_FREE;
 	ctx->vb = NULL;
 	isp_mem_destroy(ctx->dma_desc_obj);
-	for(i = 0; i < dev_priv->fmt.planes; i++) {
+	for (i = 0; i < dev_priv->fmt.planes; i++) {
 		iommu_free(dev_priv, ctx->plane[i]);
 		ctx->plane[i] = NULL;
 	}
@@ -125,7 +108,7 @@ static int fthd_send_h2t_buffer(struct fthd_private *dev_priv, struct h2t_buf_ct
 					ctx->dma_desc_obj->offset, 0x180, 0x30000000, &entry);
 
 	if (ret) {
-		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __FUNCTION__, ret);
+		pr_err("%s: fthd_channel_ringbuf_send: %d\n", __func__, ret);
 		return ret;
 	}
 	return fthd_channel_wait_ready(dev_priv, dev_priv->channel_buf_h2t, entry, 2000);
@@ -138,8 +121,9 @@ static void fthd_buffer_queue(struct vb2_buffer *vb)
 	struct h2t_buf_ctx *ctx = NULL;
 
 	int i;
+
 	pr_debug("vb = %p\n", vb);
-	for(i = 0; i < FTHD_BUFFERS; i++) {
+	for (i = 0; i < FTHD_BUFFERS; i++) {
 		if (dev_priv->h2t_bufs[i].vb == vb) {
 			ctx = dev_priv->h2t_bufs + i;
 			break;
@@ -158,7 +142,7 @@ static void fthd_buffer_queue(struct vb2_buffer *vb)
 		list = &ctx->dma_desc_list;
 		list->field0 = 1;
 		ctx->state = BUF_HW_QUEUED;
-		wmb();
+		wmb(); /* Ensure state is set before sending buffer */
 		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);
 
@@ -167,7 +151,6 @@ static void fthd_buffer_queue(struct vb2_buffer *vb)
 			ctx->state = BUF_ALLOC;
 		}
 	}
-	return;
 }
 
 static int fthd_buffer_prepare(struct vb2_buffer *vb)
@@ -179,7 +162,7 @@ static int fthd_buffer_prepare(struct vb2_buffer *vb)
 	int i;
 
 	pr_debug("%p\n", vb);
-	for(i = 0; i < FTHD_BUFFERS; i++) {
+	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;
@@ -199,11 +182,11 @@ static int fthd_buffer_prepare(struct vb2_buffer *vb)
 		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;
+		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;
 		}
 	}
 
@@ -236,7 +219,7 @@ void fthd_buffer_return_handler(struct fthd_private *dev_priv, u32 offset, int s
 
 	FTHD_S2_MEMCPY_FROMIO(&list, offset, sizeof(list));
 
-	for(i = 0; i < list.count; i++) {
+	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);
@@ -268,7 +251,7 @@ static int fthd_start_streaming(struct vb2_queue *vq, unsigned int count)
 	if (ret)
 		return ret;
 
-	for(i = 0; i < FTHD_BUFFERS && count; i++, count--) {
+	for (i = 0; i < FTHD_BUFFERS && count; i++, count--) {
 		ctx = dev_priv->h2t_bufs + i;
 		if (ctx->state != BUF_DRV_QUEUED)
 			continue;
@@ -295,31 +278,27 @@ static void fthd_stop_streaming(struct vb2_queue *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;
+		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 = {
+static const 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 = {
+static const struct v4l2_file_operations fthd_vdev_fops = {
 	.owner          = THIS_MODULE,
 	.open           = v4l2_fh_open,
 
@@ -337,7 +316,7 @@ static int fthd_v4l2_ioctl_enum_input(struct file *filp, void *priv,
 		return -EINVAL;
 
 	memset(input, 0, sizeof(*input));
-	strcpy(input->name, "Camera");
+	strscpy(input->name, "Camera", sizeof(input->name));
 	input->type = V4L2_INPUT_TYPE_CAMERA;
 	input->std = 0;
 
@@ -362,8 +341,8 @@ static int fthd_v4l2_ioctl_querycap(struct file *filp, void *priv,
 {
 	struct fthd_private *dev_priv = video_drvdata(filp);
 
-	strcpy(cap->driver, "facetimehd");
-	strcpy(cap->card, "Apple Facetime HD");
+	strscpy(cap->driver, "facetimehd", sizeof(cap->driver));
+	strscpy(cap->card, "Apple Facetime HD", sizeof(cap->card));
 	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
 		 pci_name(dev_priv->pdev));
 
@@ -398,7 +377,8 @@ static int fthd_v4l2_ioctl_enum_fmt_vid_cap(struct file *filp, void *priv,
 	}
 
 	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-	strncpy(fmt->description, desc, sizeof(fmt->description));
+	if (desc)
+		strscpy(fmt->description, desc, sizeof(fmt->description));
 
 	return 0;
 }
@@ -409,7 +389,8 @@ static int fthd_v4l2_adjust_format(struct fthd_private *dev_priv,
 
 	/* 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. */
+	 * 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;
 
@@ -453,7 +434,7 @@ static int fthd_v4l2_ioctl_try_fmt_vid_cap(struct file *filp, void *_priv,
 {
 	struct fthd_private *dev_priv = video_drvdata(filp);
 
-	pr_debug("%s: %dx%d\n", __FUNCTION__, fmt->fmt.pix.width, fmt->fmt.pix.height);
+	pr_debug("%s: %dx%d\n", __func__, fmt->fmt.pix.width, fmt->fmt.pix.height);
 
 	if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return -EINVAL;
@@ -466,7 +447,6 @@ static int fthd_v4l2_ioctl_g_fmt_vid_cap(struct file *filp, void *priv,
 {
 	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;
 
@@ -514,7 +494,8 @@ static int fthd_v4l2_ioctl_g_parm(struct file *filp, void *priv,
 	 * 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). */
+	 * (e.g. GNOME Snapshot froze, while ffplay/v4l2-ctl were unaffected).
+	 */
 	struct v4l2_fract timeperframe = {
 		.numerator = 1,
 		.denominator = 30,
@@ -533,7 +514,7 @@ 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 fthd_private *dev_priv = video_drvdata(filp);
 	struct v4l2_fract *timeperframe;
 
 	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
@@ -541,7 +522,7 @@ static int fthd_v4l2_ioctl_s_parm(struct file *filp, void *priv,
 
 	timeperframe = &parm->parm.capture.timeperframe;
 
-	if(timeperframe->denominator == 0) {
+	if (timeperframe->denominator == 0) {
 		timeperframe->numerator = 20;
 		timeperframe->denominator = 1000;
 	}
@@ -578,7 +559,6 @@ static int fthd_v4l2_ioctl_enum_frameintervals(struct file *filp, void *priv,
 	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;
@@ -611,7 +591,7 @@ static int fthd_v4l2_ioctl_subscribe_event(struct v4l2_fh *fh,
 	return -EINVAL;
 }
 
-static struct v4l2_ioctl_ops fthd_ioctl_ops = {
+static const 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,
@@ -623,7 +603,7 @@ static struct v4l2_ioctl_ops fthd_ioctl_ops = {
 	.vidioc_querycap        = fthd_v4l2_ioctl_querycap,
 
 
-        .vidioc_reqbufs         = vb2_ioctl_reqbufs,
+	.vidioc_reqbufs         = vb2_ioctl_reqbufs,
 	.vidioc_create_bufs     = vb2_ioctl_create_bufs,
 	.vidioc_querybuf        = vb2_ioctl_querybuf,
 	.vidioc_qbuf            = vb2_ioctl_qbuf,
@@ -654,7 +634,7 @@ static int fthd_s_ctrl(struct v4l2_ctrl *ctrl)
 
 	pr_info("id = %x, val = %d\n", ctrl->id, ctrl->val);
 
-	switch(ctrl->id) {
+	switch (ctrl->id) {
 	case V4L2_CID_CONTRAST:
 		ret = fthd_isp_cmd_channel_contrast_set(dev_priv, 0, ctrl->val);
 		break;
@@ -669,10 +649,9 @@ static int fthd_s_ctrl(struct v4l2_ctrl *ctrl)
 		break;
 	case V4L2_CID_AUTO_WHITE_BALANCE:
 		ret = fthd_isp_cmd_channel_awb(dev_priv, 0, ctrl->val);
-
+		break;
 	default:
 		break;
-
 	}
 	pr_debug("ret = %d\n", ret);
 	return ret;
@@ -711,11 +690,7 @@ int fthd_v4l2_register(struct fthd_private *dev_priv)
 	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);
@@ -739,21 +714,17 @@ int fthd_v4l2_register(struct fthd_private *dev_priv)
 		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?
+	strscpy(vdev->name, "Apple Facetime HD", sizeof(vdev->name));
 	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) {
@@ -761,7 +732,8 @@ int fthd_v4l2_register(struct fthd_private *dev_priv)
 		goto fail_vdev;
 	}
 	/* Default to the sensor's native resolution (detected at probe), or the
-	 * generic ceiling if detection didn't run. */
+	 * 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;
@@ -782,9 +754,6 @@ 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
index 8e4f9d574904..13856ee757f3 100644
--- a/drivers/media/pci/facetimehd/fthd_v4l2.h
+++ b/drivers/media/pci/facetimehd/fthd_v4l2.h
@@ -1,6 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * SPDX-License-Identifier: GPL-2.0-only
- *
  * FacetimeHD camera driver
  *
  * Copyright (C) 2015 Sven Schnelle <svens@stackframe.org>
@@ -28,6 +27,7 @@ struct fthd_fmt {
 };
 
 struct fthd_private;
+
 extern int fthd_v4l2_register(struct fthd_private *dev_priv);
 extern void fthd_v4l2_unregister(struct fthd_private *dev_priv);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 4/5] facetimehd: add firmware extraction script from patjak/facetimehd-firmware
  2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
                   ` (2 preceding siblings ...)
  2026-08-13 17:35 ` [PATCH 3/5] facetimehd: fix compile-time issues and formatting Jack Flusche
@ 2026-08-13 17:35 ` Jack Flusche
  2026-08-13 17:35 ` [PATCH 5/5] facetimehd: add entry to MAINTAINERS Jack Flusche
  4 siblings, 0 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

Copy script from https://github.com/patjak/facetimehd-firmware to
extract proprietary firmware for the FacetimeHD driver from an
Apple OSX update file and add a FIRMWARE file to describe usage

Signed-off-by: Jack Flusche <jackflusche@gmail.com>
---
 drivers/media/pci/facetimehd/FIRMWARE    |  6 +++
 drivers/media/pci/facetimehd/firmware.sh | 61 ++++++++++++++++++++++++
 2 files changed, 67 insertions(+)
 create mode 100644 drivers/media/pci/facetimehd/FIRMWARE
 create mode 100755 drivers/media/pci/facetimehd/firmware.sh

diff --git a/drivers/media/pci/facetimehd/FIRMWARE b/drivers/media/pci/facetimehd/FIRMWARE
new file mode 100644
index 000000000000..ba9f37aa702f
--- /dev/null
+++ b/drivers/media/pci/facetimehd/FIRMWARE
@@ -0,0 +1,6 @@
+This driver uses proprietary Apple firmware. To obtain this firmware, use the
+included firmware.sh script (copied from patjak/facetimehd-firmware). This
+script will download and extract the firmware from a Mac update file found at
+https://updates.cdn-apple.com/2019/cert/041-88431-20191011-e7ee7d98-2878-4cd9-bc0a-d98b3a1e24b1/OSXUpd10.11.5.dmg
+The script extracts the firmware to /usr/lib/firmware or /lib/firmware,
+or to a folder specified using the first argument
\ No newline at end of file
diff --git a/drivers/media/pci/facetimehd/firmware.sh b/drivers/media/pci/facetimehd/firmware.sh
new file mode 100755
index 000000000000..fc6b67149390
--- /dev/null
+++ b/drivers/media/pci/facetimehd/firmware.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+
+# This is a simplified version of the download/extract/install scripts
+# It is intended to be used by disto package installs (e.g. in %post stage)
+
+URL=https://updates.cdn-apple.com/2019/cert/041-88431-20191011-e7ee7d98-2878-4cd9-bc0a-d98b3a1e24b1/OSXUpd10.11.5.dmg
+RANGE=204909802-207733123
+OSX_DRV=AppleCameraInterface
+OSX_DRV_DIR=System/Library/Extensions/AppleCameraInterface.kext/Contents/MacOS
+FILE=$OSX_DRV_DIR/$OSX_DRV
+DRV_HASH=f56e68a880b65767335071531a1c75f3cfd4958adc6d871adf8dbf3b788e8ee1
+FW_HASH=e3e6034a67dfdaa27672dd547698bbc5b33f47f1fc7f5572a2fb68ea09d32d3d
+OFFSET=81920
+SIZE=603715
+
+if [[ "$EUID" != 0 ]]
+	then echo "Please run as root"
+	exit 1
+fi
+
+echo -e "FacetimeHD firmware download and installation script\n"
+
+echo -n "Downloading driver..."
+cd /tmp
+curl -s -L -r "$RANGE" "$URL" | xzcat -q  2> /dev/null | \
+	cpio --format odc -i -d "./$FILE" &> /dev/null
+mv $FILE .
+rm -R ./System
+echo "done"
+
+HASH=$(sha256sum $OSX_DRV | awk '{ print $1 }')
+if [[ "$HASH" != "$DRV_HASH" ]]; then
+	echo "Incorrect driver checksum. Aborting."
+	exit 1
+fi
+
+echo -n "Extracting firmware..."
+dd bs=1 skip=$OFFSET count=$SIZE if=./$OSX_DRV of=./firmware.bin.gz &> /dev/null
+rm ./$OSX_DRV
+gunzip ./firmware.bin.gz
+echo "done"
+HASH=$(sha256sum ./firmware.bin | awk '{ print $1 }')
+if [[ "$HASH" != "$FW_HASH" ]]; then
+	echo "Incorrect firmware checksum. Aborting."
+	exit 1
+fi
+
+echo -n "Installing firmware..."
+if [ -n "$1" ]; then
+    FW_DIR=$1
+elif [ -d "/usr/lib/firmware" ]; then
+	FW_DIR=/usr/lib/firmware/facetimehd
+else
+	FW_DIR=/lib/firmware/facetimehd
+fi
+
+install -dm755 $FW_DIR
+install -m644 firmware.bin $FW_DIR/firmware.bin
+rm firmware.bin
+echo "done"
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 5/5] facetimehd: add entry to MAINTAINERS
  2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
                   ` (3 preceding siblings ...)
  2026-08-13 17:35 ` [PATCH 4/5] facetimehd: add firmware extraction script from patjak/facetimehd-firmware Jack Flusche
@ 2026-08-13 17:35 ` Jack Flusche
  4 siblings, 0 replies; 6+ messages in thread
From: Jack Flusche @ 2026-08-13 17:35 UTC (permalink / raw)
  To: linux-media; +Cc: Jack Flusche, patrik.r.jakobsson, sakari.ailus, hverkuil

Add MAINTAINERS entry for the FacetimeHD driver

Signed-off-by: Jack Flusche <jackflusche@gmail.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..ba9661d4945a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5396,6 +5396,13 @@ F:	drivers/net/phy/bcm*.[ch]
 F:	drivers/net/phy/broadcom.c
 F:	include/linux/brcmphy.h
 
+BROADCOM FACETIMEHD CAMERA DRIVER
+M:	Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
+M:	Jack Flusche <jackflusche@gmail.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	drivers/media/pci/facetimehd/
+
 BROADCOM GENET ETHERNET DRIVER
 M:	Doug Berger <opendmb@gmail.com>
 M:	Florian Fainelli <florian.fainelli@broadcom.com>
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-13 17:36 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 17:35 [PATCH 0/5] Add driver for Broadcom FacetimeHD camera Jack Flusche
2026-08-13 17:35 ` [PATCH 1/5] facetimehd: copy code from patjak/facetimehd Jack Flusche
2026-08-13 17:35 ` [PATCH 2/5] facetimehd: add Kconfig and Makefile Jack Flusche
2026-08-13 17:35 ` [PATCH 3/5] facetimehd: fix compile-time issues and formatting Jack Flusche
2026-08-13 17:35 ` [PATCH 4/5] facetimehd: add firmware extraction script from patjak/facetimehd-firmware Jack Flusche
2026-08-13 17:35 ` [PATCH 5/5] facetimehd: add entry to MAINTAINERS Jack Flusche

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox