summaryrefslogtreecommitdiffstats
path: root/drivers/media/pci/solo6x10
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/pci/solo6x10
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/pci/solo6x10')
-rw-r--r--drivers/media/pci/solo6x10/Kconfig20
-rw-r--r--drivers/media/pci/solo6x10/Makefile6
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-core.c679
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-disp.c313
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-eeprom.c141
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-enc.c331
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-g723.c420
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-gpio.c195
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-i2c.c323
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-jpeg.h180
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-offsets.h74
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-p2m.c317
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-regs.h628
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-tw28.c863
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-tw28.h56
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c1394
-rw-r--r--drivers/media/pci/solo6x10/solo6x10-v4l2.c719
-rw-r--r--drivers/media/pci/solo6x10/solo6x10.h380
18 files changed, 7039 insertions, 0 deletions
diff --git a/drivers/media/pci/solo6x10/Kconfig b/drivers/media/pci/solo6x10/Kconfig
new file mode 100644
index 000000000..adb247847
--- /dev/null
+++ b/drivers/media/pci/solo6x10/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_SOLO6X10
+ tristate "Bluecherry / Softlogic 6x10 capture cards (MPEG-4/H.264)"
+ depends on PCI && VIDEO_DEV && SND && I2C
+ select BITREVERSE
+ select FONT_SUPPORT
+ select FONT_8x16
+ select VIDEOBUF2_DMA_SG
+ select VIDEOBUF2_DMA_CONTIG
+ select SND_PCM
+ select FONT_8x16
+ help
+ This driver supports the Bluecherry H.264 and MPEG-4 hardware
+ compression capture cards and other Softlogic-based ones.
+
+ Following cards have been tested:
+ * Bluecherry BC-H16480A (PCIe, 16 port, H.264)
+ * Bluecherry BC-H04120A (PCIe, 4 port, H.264)
+ * Bluecherry BC-H04120A-MPCI (Mini-PCI, 4 port, H.264)
+ * Bluecherry BC-04120A (PCIe, 4 port, MPEG-4)
diff --git a/drivers/media/pci/solo6x10/Makefile b/drivers/media/pci/solo6x10/Makefile
new file mode 100644
index 000000000..308387c86
--- /dev/null
+++ b/drivers/media/pci/solo6x10/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+solo6x10-y := solo6x10-core.o solo6x10-i2c.o solo6x10-p2m.o solo6x10-v4l2.o \
+ solo6x10-tw28.o solo6x10-gpio.o solo6x10-disp.o solo6x10-enc.o \
+ solo6x10-v4l2-enc.o solo6x10-g723.o solo6x10-eeprom.o
+
+obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10.o
diff --git a/drivers/media/pci/solo6x10/solo6x10-core.c b/drivers/media/pci/solo6x10/solo6x10-core.c
new file mode 100644
index 000000000..6d87fbb0e
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-core.c
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/videodev2.h>
+#include <linux/delay.h>
+#include <linux/sysfs.h>
+#include <linux/ktime.h>
+#include <linux/slab.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+MODULE_DESCRIPTION("Softlogic 6x10 MPEG4/H.264/G.723 CODEC V4L2/ALSA Driver");
+MODULE_AUTHOR("Bluecherry <maintainers@bluecherrydvr.com>");
+MODULE_VERSION(SOLO6X10_VERSION);
+MODULE_LICENSE("GPL");
+
+static unsigned video_nr = -1;
+module_param(video_nr, uint, 0644);
+MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)");
+
+static int full_eeprom; /* default is only top 64B */
+module_param(full_eeprom, uint, 0644);
+MODULE_PARM_DESC(full_eeprom, "Allow access to full 128B EEPROM (dangerous)");
+
+
+static void solo_set_time(struct solo_dev *solo_dev)
+{
+ struct timespec64 ts;
+
+ ktime_get_ts64(&ts);
+
+ /* no overflow because we use monotonic timestamps */
+ solo_reg_write(solo_dev, SOLO_TIMER_SEC, (u32)ts.tv_sec);
+ solo_reg_write(solo_dev, SOLO_TIMER_USEC, (u32)ts.tv_nsec / NSEC_PER_USEC);
+}
+
+static void solo_timer_sync(struct solo_dev *solo_dev)
+{
+ u32 sec, usec;
+ struct timespec64 ts;
+ long diff;
+
+ if (solo_dev->type != SOLO_DEV_6110)
+ return;
+
+ if (++solo_dev->time_sync < 60)
+ return;
+
+ solo_dev->time_sync = 0;
+
+ sec = solo_reg_read(solo_dev, SOLO_TIMER_SEC);
+ usec = solo_reg_read(solo_dev, SOLO_TIMER_USEC);
+
+ ktime_get_ts64(&ts);
+
+ diff = (s32)ts.tv_sec - (s32)sec;
+ diff = (diff * 1000000)
+ + ((s32)(ts.tv_nsec / NSEC_PER_USEC) - (s32)usec);
+
+ if (diff > 1000 || diff < -1000) {
+ solo_set_time(solo_dev);
+ } else if (diff) {
+ long usec_lsb = solo_dev->usec_lsb;
+
+ usec_lsb -= diff / 4;
+ if (usec_lsb < 0)
+ usec_lsb = 0;
+ else if (usec_lsb > 255)
+ usec_lsb = 255;
+
+ solo_dev->usec_lsb = usec_lsb;
+ solo_reg_write(solo_dev, SOLO_TIMER_USEC_LSB,
+ solo_dev->usec_lsb);
+ }
+}
+
+static irqreturn_t solo_isr(int irq, void *data)
+{
+ struct solo_dev *solo_dev = data;
+ u32 status;
+ int i;
+
+ status = solo_reg_read(solo_dev, SOLO_IRQ_STAT);
+ if (!status)
+ return IRQ_NONE;
+
+ /* Acknowledge all interrupts immediately */
+ solo_reg_write(solo_dev, SOLO_IRQ_STAT, status);
+
+ if (status & SOLO_IRQ_PCI_ERR)
+ solo_p2m_error_isr(solo_dev);
+
+ for (i = 0; i < SOLO_NR_P2M; i++)
+ if (status & SOLO_IRQ_P2M(i))
+ solo_p2m_isr(solo_dev, i);
+
+ if (status & SOLO_IRQ_IIC)
+ solo_i2c_isr(solo_dev);
+
+ if (status & SOLO_IRQ_VIDEO_IN) {
+ solo_video_in_isr(solo_dev);
+ solo_timer_sync(solo_dev);
+ }
+
+ if (status & SOLO_IRQ_ENCODER)
+ solo_enc_v4l2_isr(solo_dev);
+
+ if (status & SOLO_IRQ_G723)
+ solo_g723_isr(solo_dev);
+
+ return IRQ_HANDLED;
+}
+
+static void free_solo_dev(struct solo_dev *solo_dev)
+{
+ struct pci_dev *pdev = solo_dev->pdev;
+
+ if (solo_dev->dev.parent)
+ device_unregister(&solo_dev->dev);
+
+ if (solo_dev->reg_base) {
+ /* Bring down the sub-devices first */
+ solo_g723_exit(solo_dev);
+ solo_enc_v4l2_exit(solo_dev);
+ solo_enc_exit(solo_dev);
+ solo_v4l2_exit(solo_dev);
+ solo_disp_exit(solo_dev);
+ solo_gpio_exit(solo_dev);
+ solo_p2m_exit(solo_dev);
+ solo_i2c_exit(solo_dev);
+
+ /* Now cleanup the PCI device */
+ solo_irq_off(solo_dev, ~0);
+ free_irq(pdev->irq, solo_dev);
+ pci_iounmap(pdev, solo_dev->reg_base);
+ }
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ v4l2_device_unregister(&solo_dev->v4l2_dev);
+ pci_set_drvdata(pdev, NULL);
+
+ kfree(solo_dev);
+}
+
+static ssize_t eeprom_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ u16 *p = (u16 *)buf;
+ int i;
+
+ if (count & 0x1)
+ dev_warn(dev, "EEPROM Write not aligned (truncating)\n");
+
+ if (!full_eeprom && count > 64) {
+ dev_warn(dev, "EEPROM Write truncated to 64 bytes\n");
+ count = 64;
+ } else if (full_eeprom && count > 128) {
+ dev_warn(dev, "EEPROM Write truncated to 128 bytes\n");
+ count = 128;
+ }
+
+ solo_eeprom_ewen(solo_dev, 1);
+
+ for (i = full_eeprom ? 0 : 32; i < min((int)(full_eeprom ? 64 : 32),
+ (int)(count / 2)); i++)
+ solo_eeprom_write(solo_dev, i, cpu_to_be16(p[i]));
+
+ solo_eeprom_ewen(solo_dev, 0);
+
+ return count;
+}
+
+static ssize_t eeprom_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ u16 *p = (u16 *)buf;
+ int count = (full_eeprom ? 128 : 64);
+ int i;
+
+ for (i = (full_eeprom ? 0 : 32); i < (count / 2); i++)
+ p[i] = be16_to_cpu(solo_eeprom_read(solo_dev, i));
+
+ return count;
+}
+
+static ssize_t p2m_timeouts_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%d\n", solo_dev->p2m_timeouts);
+}
+
+static ssize_t sdram_size_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%dMegs\n", solo_dev->sdram_size >> 20);
+}
+
+static ssize_t tw28xx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "tw2815[%d] tw2864[%d] tw2865[%d]\n",
+ hweight32(solo_dev->tw2815),
+ hweight32(solo_dev->tw2864),
+ hweight32(solo_dev->tw2865));
+}
+
+static ssize_t input_map_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned int val;
+ char *out = buf;
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_0);
+ out += sprintf(out, "Channel 0 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 1 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 2 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 3 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Channel 4 => Input %d\n", (val >> 20) & 0x1f);
+ out += sprintf(out, "Channel 5 => Input %d\n", (val >> 25) & 0x1f);
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_1);
+ out += sprintf(out, "Channel 6 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 7 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 8 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 9 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Channel 10 => Input %d\n", (val >> 20) & 0x1f);
+ out += sprintf(out, "Channel 11 => Input %d\n", (val >> 25) & 0x1f);
+
+ val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_2);
+ out += sprintf(out, "Channel 12 => Input %d\n", val & 0x1f);
+ out += sprintf(out, "Channel 13 => Input %d\n", (val >> 5) & 0x1f);
+ out += sprintf(out, "Channel 14 => Input %d\n", (val >> 10) & 0x1f);
+ out += sprintf(out, "Channel 15 => Input %d\n", (val >> 15) & 0x1f);
+ out += sprintf(out, "Spot Output => Input %d\n", (val >> 20) & 0x1f);
+
+ return out - buf;
+}
+
+static ssize_t p2m_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ unsigned long ms;
+ int ret = kstrtoul(buf, 10, &ms);
+
+ if (ret < 0 || ms > 200)
+ return -EINVAL;
+ solo_dev->p2m_jiffies = msecs_to_jiffies(ms);
+
+ return count;
+}
+
+static ssize_t p2m_timeout_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+
+ return sprintf(buf, "%ums\n", jiffies_to_msecs(solo_dev->p2m_jiffies));
+}
+
+static ssize_t intervals_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ char *out = buf;
+ int fps = solo_dev->fps;
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ out += sprintf(out, "Channel %d: %d/%d (0x%08x)\n",
+ i, solo_dev->v4l2_enc[i]->interval, fps,
+ solo_reg_read(solo_dev, SOLO_CAP_CH_INTV(i)));
+ }
+
+ return out - buf;
+}
+
+static ssize_t sdram_offsets_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ char *out = buf;
+
+ out += sprintf(out, "DISP: 0x%08x @ 0x%08x\n",
+ SOLO_DISP_EXT_ADDR,
+ SOLO_DISP_EXT_SIZE);
+
+ out += sprintf(out, "EOSD: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_EOSD_EXT_ADDR,
+ SOLO_EOSD_EXT_AREA(solo_dev),
+ SOLO_EOSD_EXT_SIZE(solo_dev),
+ SOLO_EOSD_EXT_AREA(solo_dev) /
+ SOLO_EOSD_EXT_SIZE(solo_dev));
+
+ out += sprintf(out, "MOTI: 0x%08x @ 0x%08x\n",
+ SOLO_MOTION_EXT_ADDR(solo_dev),
+ SOLO_MOTION_EXT_SIZE);
+
+ out += sprintf(out, "G723: 0x%08x @ 0x%08x\n",
+ SOLO_G723_EXT_ADDR(solo_dev),
+ SOLO_G723_EXT_SIZE);
+
+ out += sprintf(out, "CAPT: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_CAP_EXT_ADDR(solo_dev),
+ SOLO_CAP_EXT_SIZE(solo_dev),
+ SOLO_CAP_PAGE_SIZE,
+ SOLO_CAP_EXT_SIZE(solo_dev) / SOLO_CAP_PAGE_SIZE);
+
+ out += sprintf(out, "EREF: 0x%08x @ 0x%08x (0x%08x * %d)\n",
+ SOLO_EREF_EXT_ADDR(solo_dev),
+ SOLO_EREF_EXT_AREA(solo_dev),
+ SOLO_EREF_EXT_SIZE,
+ SOLO_EREF_EXT_AREA(solo_dev) / SOLO_EREF_EXT_SIZE);
+
+ out += sprintf(out, "MPEG: 0x%08x @ 0x%08x\n",
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ SOLO_MP4E_EXT_SIZE(solo_dev));
+
+ out += sprintf(out, "JPEG: 0x%08x @ 0x%08x\n",
+ SOLO_JPEG_EXT_ADDR(solo_dev),
+ SOLO_JPEG_EXT_SIZE(solo_dev));
+
+ return out - buf;
+}
+
+static ssize_t sdram_show(struct file *file, struct kobject *kobj,
+ struct bin_attribute *a, char *buf,
+ loff_t off, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct solo_dev *solo_dev =
+ container_of(dev, struct solo_dev, dev);
+ const int size = solo_dev->sdram_size;
+
+ if (off >= size)
+ return 0;
+
+ if (off + count > size)
+ count = size - off;
+
+ if (solo_p2m_dma(solo_dev, 0, buf, off, count, 0, 0))
+ return -EIO;
+
+ return count;
+}
+
+static const struct device_attribute solo_dev_attrs[] = {
+ __ATTR(eeprom, 0640, eeprom_show, eeprom_store),
+ __ATTR(p2m_timeout, 0644, p2m_timeout_show, p2m_timeout_store),
+ __ATTR_RO(p2m_timeouts),
+ __ATTR_RO(sdram_size),
+ __ATTR_RO(tw28xx),
+ __ATTR_RO(input_map),
+ __ATTR_RO(intervals),
+ __ATTR_RO(sdram_offsets),
+};
+
+static void solo_device_release(struct device *dev)
+{
+ /* Do nothing */
+}
+
+static int solo_sysfs_init(struct solo_dev *solo_dev)
+{
+ struct bin_attribute *sdram_attr = &solo_dev->sdram_attr;
+ struct device *dev = &solo_dev->dev;
+ const char *driver;
+ int i;
+
+ if (solo_dev->type == SOLO_DEV_6110)
+ driver = "solo6110";
+ else
+ driver = "solo6010";
+
+ dev->release = solo_device_release;
+ dev->parent = &solo_dev->pdev->dev;
+ set_dev_node(dev, dev_to_node(&solo_dev->pdev->dev));
+ dev_set_name(dev, "%s-%d-%d", driver, solo_dev->vfd->num,
+ solo_dev->nr_chans);
+
+ if (device_register(dev)) {
+ put_device(dev);
+ dev->parent = NULL;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(solo_dev_attrs); i++) {
+ if (device_create_file(dev, &solo_dev_attrs[i])) {
+ device_unregister(dev);
+ return -ENOMEM;
+ }
+ }
+
+ sysfs_attr_init(&sdram_attr->attr);
+ sdram_attr->attr.name = "sdram";
+ sdram_attr->attr.mode = 0440;
+ sdram_attr->read = sdram_show;
+ sdram_attr->size = solo_dev->sdram_size;
+
+ if (device_create_bin_file(dev, sdram_attr)) {
+ device_unregister(dev);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct solo_dev *solo_dev;
+ int ret;
+ u8 chip_id;
+
+ solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL);
+ if (solo_dev == NULL)
+ return -ENOMEM;
+
+ if (id->driver_data == SOLO_DEV_6010)
+ dev_info(&pdev->dev, "Probing Softlogic 6010\n");
+ else
+ dev_info(&pdev->dev, "Probing Softlogic 6110\n");
+
+ solo_dev->type = id->driver_data;
+ solo_dev->pdev = pdev;
+ ret = v4l2_device_register(&pdev->dev, &solo_dev->v4l2_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Only for during init */
+ solo_dev->p2m_jiffies = msecs_to_jiffies(100);
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ goto fail_probe;
+
+ pci_set_master(pdev);
+
+ /* RETRY/TRDY Timeout disabled */
+ pci_write_config_byte(pdev, 0x40, 0x00);
+ pci_write_config_byte(pdev, 0x41, 0x00);
+
+ ret = pci_request_regions(pdev, SOLO6X10_NAME);
+ if (ret)
+ goto fail_probe;
+
+ solo_dev->reg_base = pci_ioremap_bar(pdev, 0);
+ if (solo_dev->reg_base == NULL) {
+ ret = -ENOMEM;
+ goto fail_probe;
+ }
+
+ chip_id = solo_reg_read(solo_dev, SOLO_CHIP_OPTION) &
+ SOLO_CHIP_ID_MASK;
+ switch (chip_id) {
+ case 7:
+ solo_dev->nr_chans = 16;
+ solo_dev->nr_ext = 5;
+ break;
+ case 6:
+ solo_dev->nr_chans = 8;
+ solo_dev->nr_ext = 2;
+ break;
+ default:
+ dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n",
+ chip_id);
+ fallthrough;
+ case 5:
+ solo_dev->nr_chans = 4;
+ solo_dev->nr_ext = 1;
+ }
+
+ /* Disable all interrupts to start */
+ solo_irq_off(solo_dev, ~0);
+
+ /* Initial global settings */
+ if (solo_dev->type == SOLO_DEV_6010) {
+ solo_dev->clock_mhz = 108;
+ solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT
+ | SOLO_SYS_CFG_INPUTDIV(25)
+ | SOLO_SYS_CFG_FEEDBACKDIV(solo_dev->clock_mhz * 2 - 2)
+ | SOLO_SYS_CFG_OUTDIV(3);
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+ } else {
+ u32 divq, divf;
+
+ solo_dev->clock_mhz = 135;
+
+ if (solo_dev->clock_mhz < 125) {
+ divq = 3;
+ divf = (solo_dev->clock_mhz * 4) / 3 - 1;
+ } else {
+ divq = 2;
+ divf = (solo_dev->clock_mhz * 2) / 3 - 1;
+ }
+
+ solo_reg_write(solo_dev, SOLO_PLL_CONFIG,
+ (1 << 20) | /* PLL_RANGE */
+ (8 << 15) | /* PLL_DIVR */
+ (divq << 12) |
+ (divf << 4) |
+ (1 << 1) /* PLL_FSEN */);
+
+ solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT;
+ }
+
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+ solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM,
+ solo_dev->clock_mhz - 1);
+
+ /* PLL locking time of 1ms */
+ mdelay(1);
+
+ ret = request_irq(pdev->irq, solo_isr, IRQF_SHARED, SOLO6X10_NAME,
+ solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Handle this from the start */
+ solo_irq_on(solo_dev, SOLO_IRQ_PCI_ERR);
+
+ ret = solo_i2c_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Setup the DMA engine */
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL,
+ SOLO_DMA_CTRL_REFRESH_CYCLE(1) |
+ SOLO_DMA_CTRL_SDRAM_SIZE(2) |
+ SOLO_DMA_CTRL_SDRAM_CLK_INVERT |
+ SOLO_DMA_CTRL_READ_CLK_SELECT |
+ SOLO_DMA_CTRL_LATENCY(1));
+
+ /* Undocumented crap */
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL1,
+ solo_dev->type == SOLO_DEV_6010 ? 0x100 : 0x300);
+
+ if (solo_dev->type != SOLO_DEV_6010) {
+ solo_dev->usec_lsb = 0x3f;
+ solo_set_time(solo_dev);
+ }
+
+ /* Disable watchdog */
+ solo_reg_write(solo_dev, SOLO_WATCHDOG, 0);
+
+ /* Initialize sub components */
+
+ ret = solo_p2m_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_disp_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_gpio_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_tw28_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_v4l2_init(solo_dev, video_nr);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_enc_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_enc_v4l2_init(solo_dev, video_nr);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_g723_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ ret = solo_sysfs_init(solo_dev);
+ if (ret)
+ goto fail_probe;
+
+ /* Now that init is over, set this lower */
+ solo_dev->p2m_jiffies = msecs_to_jiffies(20);
+
+ return 0;
+
+fail_probe:
+ free_solo_dev(solo_dev);
+ return ret;
+}
+
+static void solo_pci_remove(struct pci_dev *pdev)
+{
+ struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev);
+ struct solo_dev *solo_dev = container_of(v4l2_dev, struct solo_dev, v4l2_dev);
+
+ free_solo_dev(solo_dev);
+}
+
+static const struct pci_device_id solo_id_table[] = {
+ /* 6010 based cards */
+ { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9),
+ .driver_data = SOLO_DEV_6010 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16),
+ .driver_data = SOLO_DEV_6010 },
+ /* 6110 based cards */
+ { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8),
+ .driver_data = SOLO_DEV_6110 },
+ { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16),
+ .driver_data = SOLO_DEV_6110 },
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, solo_id_table);
+
+static struct pci_driver solo_pci_driver = {
+ .name = SOLO6X10_NAME,
+ .id_table = solo_id_table,
+ .probe = solo_pci_probe,
+ .remove = solo_pci_remove,
+};
+
+module_pci_driver(solo_pci_driver);
diff --git a/drivers/media/pci/solo6x10/solo6x10-disp.c b/drivers/media/pci/solo6x10/solo6x10-disp.c
new file mode 100644
index 000000000..ad98ca7fb
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-disp.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+
+#include "solo6x10.h"
+
+#define SOLO_VCLK_DELAY 3
+#define SOLO_PROGRESSIVE_VSIZE 1024
+
+#define SOLO_MOT_THRESH_W 64
+#define SOLO_MOT_THRESH_H 64
+#define SOLO_MOT_THRESH_SIZE 8192
+#define SOLO_MOT_THRESH_REAL (SOLO_MOT_THRESH_W * SOLO_MOT_THRESH_H)
+#define SOLO_MOT_FLAG_SIZE 1024
+#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 16)
+
+static void solo_vin_config(struct solo_dev *solo_dev)
+{
+ solo_dev->vin_hstart = 8;
+ solo_dev->vin_vstart = 2;
+
+ solo_reg_write(solo_dev, SOLO_SYS_VCLK,
+ SOLO_VCLK_SELECT(2) |
+ SOLO_VCLK_VIN1415_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN1213_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN1011_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0809_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0607_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0405_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0203_DELAY(SOLO_VCLK_DELAY) |
+ SOLO_VCLK_VIN0001_DELAY(SOLO_VCLK_DELAY));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_I_P,
+ SOLO_VI_H_START(solo_dev->vin_hstart) |
+ SOLO_VI_V_START(solo_dev->vin_vstart) |
+ SOLO_VI_V_STOP(solo_dev->vin_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_I_S,
+ SOLO_VI_H_START(solo_dev->vout_hstart) |
+ SOLO_VI_V_START(solo_dev->vout_vstart) |
+ SOLO_VI_V_STOP(solo_dev->vout_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VI_ACT_P,
+ SOLO_VI_H_START(0) |
+ SOLO_VI_V_START(1) |
+ SOLO_VI_V_STOP(SOLO_PROGRESSIVE_VSIZE));
+
+ solo_reg_write(solo_dev, SOLO_VI_CH_FORMAT,
+ SOLO_VI_FD_SEL_MASK(0) | SOLO_VI_PROG_MASK(0));
+
+ /* On 6110, initialize mozaic darkness strength */
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0);
+ else
+ solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 16 << 22);
+
+ solo_reg_write(solo_dev, SOLO_VI_PAGE_SW, 2);
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG,
+ SOLO_VI_PB_USER_MODE);
+ solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV,
+ SOLO_VI_PB_HSIZE(858) | SOLO_VI_PB_VSIZE(246));
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V,
+ SOLO_VI_PB_VSTART(4) |
+ SOLO_VI_PB_VSTOP(4 + 240));
+ } else {
+ solo_reg_write(solo_dev, SOLO_VI_PB_CONFIG,
+ SOLO_VI_PB_USER_MODE | SOLO_VI_PB_PAL);
+ solo_reg_write(solo_dev, SOLO_VI_PB_RANGE_HV,
+ SOLO_VI_PB_HSIZE(864) | SOLO_VI_PB_VSIZE(294));
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_V,
+ SOLO_VI_PB_VSTART(4) |
+ SOLO_VI_PB_VSTOP(4 + 288));
+ }
+ solo_reg_write(solo_dev, SOLO_VI_PB_ACT_H, SOLO_VI_PB_HSTART(16) |
+ SOLO_VI_PB_HSTOP(16 + 720));
+}
+
+static void solo_vout_config_cursor(struct solo_dev *dev)
+{
+ int i;
+
+ /* Load (blank) cursor bitmap mask (2bpp) */
+ for (i = 0; i < 20; i++)
+ solo_reg_write(dev, SOLO_VO_CURSOR_MASK(i), 0);
+
+ solo_reg_write(dev, SOLO_VO_CURSOR_POS, 0);
+
+ solo_reg_write(dev, SOLO_VO_CURSOR_CLR,
+ (0x80 << 24) | (0x80 << 16) | (0x10 << 8) | 0x80);
+ solo_reg_write(dev, SOLO_VO_CURSOR_CLR2, (0xe0 << 8) | 0x80);
+}
+
+static void solo_vout_config(struct solo_dev *solo_dev)
+{
+ solo_dev->vout_hstart = 6;
+ solo_dev->vout_vstart = 8;
+
+ solo_reg_write(solo_dev, SOLO_VO_FMT_ENC,
+ solo_dev->video_type |
+ SOLO_VO_USER_COLOR_SET_NAV |
+ SOLO_VO_USER_COLOR_SET_NAH |
+ SOLO_VO_NA_COLOR_Y(0) |
+ SOLO_VO_NA_COLOR_CB(0) |
+ SOLO_VO_NA_COLOR_CR(0));
+
+ solo_reg_write(solo_dev, SOLO_VO_ACT_H,
+ SOLO_VO_H_START(solo_dev->vout_hstart) |
+ SOLO_VO_H_STOP(solo_dev->vout_hstart +
+ solo_dev->video_hsize));
+
+ solo_reg_write(solo_dev, SOLO_VO_ACT_V,
+ SOLO_VO_V_START(solo_dev->vout_vstart) |
+ SOLO_VO_V_STOP(solo_dev->vout_vstart +
+ solo_dev->video_vsize));
+
+ solo_reg_write(solo_dev, SOLO_VO_RANGE_HV,
+ SOLO_VO_H_LEN(solo_dev->video_hsize) |
+ SOLO_VO_V_LEN(solo_dev->video_vsize));
+
+ /* Border & background colors */
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_COLOR,
+ (0xa0 << 24) | (0x88 << 16) | (0xa0 << 8) | 0x88);
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_COLOR,
+ (0x10 << 24) | (0x8f << 16) | (0x10 << 8) | 0x8f);
+ solo_reg_write(solo_dev, SOLO_VO_BKG_COLOR,
+ (16 << 24) | (128 << 16) | (16 << 8) | 128);
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
+
+ solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, SOLO_VO_DISP_ON |
+ SOLO_VO_DISP_ERASE_COUNT(8) |
+ SOLO_VO_DISP_BASE(SOLO_DISP_EXT_ADDR));
+
+
+ solo_vout_config_cursor(solo_dev);
+
+ /* Enable channels we support */
+ solo_reg_write(solo_dev, SOLO_VI_CH_ENA,
+ (1 << solo_dev->nr_chans) - 1);
+}
+
+static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off,
+ u16 val, int reg_size)
+{
+ __le16 *buf;
+ const int n = 64, size = n * sizeof(*buf);
+ int i, ret = 0;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++)
+ buf[i] = cpu_to_le16(val);
+
+ for (i = 0; i < reg_size; i += size) {
+ ret = solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_MOTION_EXT_ADDR(solo_dev) + off + i,
+ size, 0, 0);
+
+ if (ret)
+ break;
+ }
+
+ kfree(buf);
+ return ret;
+}
+
+int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val)
+{
+ if (ch > solo_dev->nr_chans)
+ return -EINVAL;
+
+ return solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA +
+ (ch * SOLO_MOT_THRESH_SIZE * 2),
+ val, SOLO_MOT_THRESH_SIZE);
+}
+
+int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch,
+ const u16 *thresholds)
+{
+ const unsigned size = sizeof(u16) * 64;
+ u32 off = SOLO_MOT_FLAG_AREA + ch * SOLO_MOT_THRESH_SIZE * 2;
+ __le16 *buf;
+ int x, y;
+ int ret = 0;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+ for (y = 0; y < SOLO_MOTION_SZ; y++) {
+ for (x = 0; x < SOLO_MOTION_SZ; x++)
+ buf[x] = cpu_to_le16(thresholds[y * SOLO_MOTION_SZ + x]);
+ ret |= solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_MOTION_EXT_ADDR(solo_dev) + off + y * size,
+ size, 0, 0);
+ }
+ kfree(buf);
+ return ret;
+}
+
+/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k
+ * threshold and working table for each channel. At least that's what the
+ * spec says. However, this code (taken from rdk) has some mystery 8k
+ * block right after the flag area, before the first thresh table. */
+static void solo_motion_config(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ /* Clear motion flag area */
+ solo_dma_vin_region(solo_dev, i * SOLO_MOT_FLAG_SIZE, 0x0000,
+ SOLO_MOT_FLAG_SIZE);
+
+ /* Clear working cache table */
+ solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA +
+ (i * SOLO_MOT_THRESH_SIZE * 2) +
+ SOLO_MOT_THRESH_SIZE, 0x0000,
+ SOLO_MOT_THRESH_SIZE);
+
+ /* Set default threshold table */
+ solo_set_motion_threshold(solo_dev, i, SOLO_DEF_MOT_THRESH);
+ }
+
+ /* Default motion settings */
+ solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, SOLO_VI_MOTION_EN(0) |
+ (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16));
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CTRL,
+ SOLO_VI_MOTION_FRAME_COUNT(3) |
+ SOLO_VI_MOTION_SAMPLE_LENGTH(solo_dev->video_hsize / 16)
+ /* | SOLO_VI_MOTION_INTR_START_STOP */
+ | SOLO_VI_MOTION_SAMPLE_COUNT(10));
+
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0);
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0);
+}
+
+int solo_disp_init(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_dev->video_hsize = 704;
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ solo_dev->video_vsize = 240;
+ solo_dev->fps = 30;
+ } else {
+ solo_dev->video_vsize = 288;
+ solo_dev->fps = 25;
+ }
+
+ solo_vin_config(solo_dev);
+ solo_motion_config(solo_dev);
+ solo_vout_config(solo_dev);
+
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 1);
+
+ return 0;
+}
+
+void solo_disp_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_reg_write(solo_dev, SOLO_VO_DISP_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_ZOOM_CTRL, 0);
+ solo_reg_write(solo_dev, SOLO_VO_FREEZE_CTRL, 0);
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(i), 0);
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(i), 0);
+ solo_reg_write(solo_dev, SOLO_VI_WIN_ON(i), 0);
+ }
+
+ /* Set default border */
+ for (i = 0; i < 5; i++)
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_X(i), 0);
+
+ for (i = 0; i < 5; i++)
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_Y(i), 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_LINE_MASK, 0);
+ solo_reg_write(solo_dev, SOLO_VO_BORDER_FILL_MASK, 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(0), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(0), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(0), 0);
+
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_CTRL(1), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_START(1), 0);
+ solo_reg_write(solo_dev, SOLO_VO_RECTANGLE_STOP(1), 0);
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-eeprom.c b/drivers/media/pci/solo6x10/solo6x10-eeprom.c
new file mode 100644
index 000000000..0d864b8ca
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-eeprom.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "solo6x10.h"
+
+/* Control */
+#define EE_SHIFT_CLK 0x04
+#define EE_CS 0x08
+#define EE_DATA_WRITE 0x02
+#define EE_DATA_READ 0x01
+#define EE_ENB (0x80 | EE_CS)
+
+#define eeprom_delay() udelay(100)
+#if 0
+#define eeprom_delay() solo_reg_read(solo_dev, SOLO_EEPROM_CTRL)
+#define eeprom_delay() ({ \
+ int i, ret; \
+ udelay(100); \
+ for (i = ret = 0; i < 1000 && !ret; i++) \
+ ret = solo_eeprom_reg_read(solo_dev); \
+})
+#endif
+#define ADDR_LEN 6
+
+/* Commands */
+#define EE_EWEN_CMD 4
+#define EE_EWDS_CMD 4
+#define EE_WRITE_CMD 5
+#define EE_READ_CMD 6
+#define EE_ERASE_CMD 7
+
+static unsigned int solo_eeprom_reg_read(struct solo_dev *solo_dev)
+{
+ return solo_reg_read(solo_dev, SOLO_EEPROM_CTRL) & EE_DATA_READ;
+}
+
+static void solo_eeprom_reg_write(struct solo_dev *solo_dev, u32 data)
+{
+ solo_reg_write(solo_dev, SOLO_EEPROM_CTRL, data);
+ eeprom_delay();
+}
+
+static void solo_eeprom_cmd(struct solo_dev *solo_dev, int cmd)
+{
+ int i;
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ACCESS_EN);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+
+ for (i = 4 + ADDR_LEN; i >= 0; i--) {
+ int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0;
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE | dataval);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK | dataval);
+ }
+
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+}
+
+unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en)
+{
+ int ewen_cmd = (w_en ? 0x3f : 0) | (EE_EWEN_CMD << ADDR_LEN);
+ unsigned int retval = 0;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, ewen_cmd);
+
+ for (i = 0; i < 16; i++) {
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ }
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+
+ return retval;
+}
+
+__be16 solo_eeprom_read(struct solo_dev *solo_dev, int loc)
+{
+ int read_cmd = loc | (EE_READ_CMD << ADDR_LEN);
+ u16 retval = 0;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, read_cmd);
+
+ for (i = 0; i < 16; i++) {
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE |
+ EE_SHIFT_CLK);
+ retval = (retval << 1) | solo_eeprom_reg_read(solo_dev);
+ solo_eeprom_reg_write(solo_dev, SOLO_EEPROM_ENABLE);
+ }
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+
+ return (__force __be16)retval;
+}
+
+int solo_eeprom_write(struct solo_dev *solo_dev, int loc,
+ __be16 data)
+{
+ int write_cmd = loc | (EE_WRITE_CMD << ADDR_LEN);
+ unsigned int retval;
+ int i;
+
+ solo_eeprom_cmd(solo_dev, write_cmd);
+
+ for (i = 15; i >= 0; i--) {
+ unsigned int dataval = ((__force unsigned)data >> i) & 1;
+
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+ solo_eeprom_reg_write(solo_dev,
+ EE_ENB | (dataval << 1) | EE_SHIFT_CLK);
+ }
+
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+ solo_eeprom_reg_write(solo_dev, EE_ENB);
+
+ for (i = retval = 0; i < 10000 && !retval; i++)
+ retval = solo_eeprom_reg_read(solo_dev);
+
+ solo_eeprom_reg_write(solo_dev, ~EE_CS);
+
+ return !retval;
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-enc.c b/drivers/media/pci/solo6x10/solo6x10-enc.c
new file mode 100644
index 000000000..14a1d51cf
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-enc.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/font.h>
+#include <linux/bitrev.h>
+#include <linux/slab.h>
+
+#include "solo6x10.h"
+
+#define VI_PROG_HSIZE (1280 - 16)
+#define VI_PROG_VSIZE (1024 - 16)
+
+#define IRQ_LEVEL 2
+
+static void solo_capture_config(struct solo_dev *solo_dev)
+{
+ unsigned long height;
+ unsigned long width;
+ void *buf;
+ int i;
+
+ solo_reg_write(solo_dev, SOLO_CAP_BASE,
+ SOLO_CAP_MAX_PAGE((SOLO_CAP_EXT_SIZE(solo_dev)
+ - SOLO_CAP_PAGE_SIZE) >> 16)
+ | SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16));
+
+ /* XXX: Undocumented bits at b17 and b24 */
+ if (solo_dev->type == SOLO_DEV_6110) {
+ /* NOTE: Ref driver has (62 << 24) here as well, but it causes
+ * wacked out frame timing on 4-port 6110. */
+ solo_reg_write(solo_dev, SOLO_CAP_BTW,
+ (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) |
+ SOLO_CAP_MAX_BANDWIDTH(36));
+ } else {
+ solo_reg_write(solo_dev, SOLO_CAP_BTW,
+ (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) |
+ SOLO_CAP_MAX_BANDWIDTH(32));
+ }
+
+ /* Set scale 1, 9 dimension */
+ width = solo_dev->video_hsize;
+ height = solo_dev->video_vsize;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE1,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 2, 10 dimension */
+ width = solo_dev->video_hsize / 2;
+ height = solo_dev->video_vsize;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE2,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 3, 11 dimension */
+ width = solo_dev->video_hsize / 2;
+ height = solo_dev->video_vsize / 2;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE3,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 4, 12 dimension */
+ width = solo_dev->video_hsize / 3;
+ height = solo_dev->video_vsize / 3;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE4,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Set scale 5, 13 dimension */
+ width = solo_dev->video_hsize / 4;
+ height = solo_dev->video_vsize / 2;
+ solo_reg_write(solo_dev, SOLO_DIM_SCALE5,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 8) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Progressive */
+ width = VI_PROG_HSIZE;
+ height = VI_PROG_VSIZE;
+ solo_reg_write(solo_dev, SOLO_DIM_PROG,
+ SOLO_DIM_H_MB_NUM(width / 16) |
+ SOLO_DIM_V_MB_NUM_FRAME(height / 16) |
+ SOLO_DIM_V_MB_NUM_FIELD(height / 16));
+
+ /* Clear OSD */
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CH, 0);
+ solo_reg_write(solo_dev, SOLO_VE_OSD_BASE, SOLO_EOSD_EXT_ADDR >> 16);
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CLR,
+ 0xF0 << 16 | 0x80 << 8 | 0x80);
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_reg_write(solo_dev, SOLO_VE_OSD_OPT,
+ SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW);
+ else
+ solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, SOLO_VE_OSD_V_DOUBLE
+ | SOLO_VE_OSD_H_SHADOW | SOLO_VE_OSD_V_SHADOW);
+
+ /* Clear OSG buffer */
+ buf = kzalloc(SOLO_EOSD_EXT_SIZE(solo_dev), GFP_KERNEL);
+ if (!buf)
+ return;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_EOSD_EXT_ADDR +
+ (SOLO_EOSD_EXT_SIZE(solo_dev) * i),
+ SOLO_EOSD_EXT_SIZE(solo_dev), 0, 0);
+ }
+ kfree(buf);
+}
+
+#define SOLO_OSD_WRITE_SIZE (16 * OSD_TEXT_MAX)
+
+/* Should be called with enable_lock held */
+int solo_osd_print(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ u8 *str = solo_enc->osd_text;
+ u8 *buf = solo_enc->osd_buf;
+ u32 reg;
+ const struct font_desc *vga = find_font("VGA8x16");
+ const u8 *vga_data;
+ int i, j;
+
+ if (WARN_ON_ONCE(!vga))
+ return -ENODEV;
+
+ reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH);
+ if (!*str) {
+ /* Disable OSD on this channel */
+ reg &= ~(1 << solo_enc->ch);
+ goto out;
+ }
+
+ memset(buf, 0, SOLO_OSD_WRITE_SIZE);
+ vga_data = (const u8 *)vga->data;
+
+ for (i = 0; *str; i++, str++) {
+ for (j = 0; j < 16; j++) {
+ buf[(j << 1) | (i & 1) | ((i & ~1) << 4)] =
+ bitrev8(vga_data[(*str << 4) | j]);
+ }
+ }
+
+ solo_p2m_dma(solo_dev, 1, buf,
+ SOLO_EOSD_EXT_ADDR_CHAN(solo_dev, solo_enc->ch),
+ SOLO_OSD_WRITE_SIZE, 0, 0);
+
+ /* Enable OSD on this channel */
+ reg |= (1 << solo_enc->ch);
+
+out:
+ solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg);
+ return 0;
+}
+
+/*
+ * Set channel Quality Profile (0-3).
+ */
+void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch,
+ unsigned int qp)
+{
+ unsigned long flags;
+ unsigned int idx, reg;
+
+ if ((ch > 31) || (qp > 3))
+ return;
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ return;
+
+ if (ch < 16) {
+ idx = 0;
+ reg = SOLO_VE_JPEG_QP_CH_L;
+ } else {
+ ch -= 16;
+ idx = 1;
+ reg = SOLO_VE_JPEG_QP_CH_H;
+ }
+ ch *= 2;
+
+ spin_lock_irqsave(&solo_dev->jpeg_qp_lock, flags);
+
+ solo_dev->jpeg_qp[idx] &= ~(3 << ch);
+ solo_dev->jpeg_qp[idx] |= (qp & 3) << ch;
+
+ solo_reg_write(solo_dev, reg, solo_dev->jpeg_qp[idx]);
+
+ spin_unlock_irqrestore(&solo_dev->jpeg_qp_lock, flags);
+}
+
+int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch)
+{
+ int idx;
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ return 2;
+
+ if (WARN_ON_ONCE(ch > 31))
+ return 2;
+
+ if (ch < 16) {
+ idx = 0;
+ } else {
+ ch -= 16;
+ idx = 1;
+ }
+ ch *= 2;
+
+ return (solo_dev->jpeg_qp[idx] >> ch) & 3;
+}
+
+#define SOLO_QP_INIT 0xaaaaaaaa
+
+static void solo_jpeg_config(struct solo_dev *solo_dev)
+{
+ if (solo_dev->type == SOLO_DEV_6010) {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL,
+ (2 << 24) | (2 << 16) | (2 << 8) | 2);
+ } else {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL,
+ (4 << 24) | (3 << 16) | (2 << 8) | 1);
+ }
+
+ spin_lock_init(&solo_dev->jpeg_qp_lock);
+
+ /* Initialize Quality Profile for all channels */
+ solo_dev->jpeg_qp[0] = solo_dev->jpeg_qp[1] = SOLO_QP_INIT;
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, SOLO_QP_INIT);
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, SOLO_QP_INIT);
+
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG,
+ (SOLO_JPEG_EXT_SIZE(solo_dev) & 0xffff0000) |
+ ((SOLO_JPEG_EXT_ADDR(solo_dev) >> 16) & 0x0000ffff));
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CTRL, 0xffffffff);
+ if (solo_dev->type == SOLO_DEV_6110) {
+ solo_reg_write(solo_dev, SOLO_VE_JPEG_CFG1,
+ (0 << 16) | (30 << 8) | 60);
+ }
+}
+
+static void solo_mp4e_config(struct solo_dev *solo_dev)
+{
+ int i;
+ u32 cfg;
+
+ solo_reg_write(solo_dev, SOLO_VE_CFG0,
+ SOLO_VE_INTR_CTRL(IRQ_LEVEL) |
+ SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) |
+ SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16));
+
+
+ cfg = SOLO_VE_BYTE_ALIGN(2) | SOLO_VE_INSERT_INDEX
+ | SOLO_VE_MOTION_MODE(0);
+ if (solo_dev->type != SOLO_DEV_6010) {
+ cfg |= SOLO_VE_MPEG_SIZE_H(
+ (SOLO_MP4E_EXT_SIZE(solo_dev) >> 24) & 0x0f);
+ cfg |= SOLO_VE_JPEG_SIZE_H(
+ (SOLO_JPEG_EXT_SIZE(solo_dev) >> 24) & 0x0f);
+ }
+ solo_reg_write(solo_dev, SOLO_VE_CFG1, cfg);
+
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_POLY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_VMRK_INIT_KEY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_STRL, 0);
+ if (solo_dev->type == SOLO_DEV_6110)
+ solo_reg_write(solo_dev, SOLO_VE_WMRK_ENABLE, 0);
+ solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0);
+ solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0);
+
+ solo_reg_write(solo_dev, SOLO_VE_ATTR,
+ SOLO_VE_LITTLE_ENDIAN |
+ SOLO_COMP_ATTR_FCODE(1) |
+ SOLO_COMP_TIME_INC(0) |
+ SOLO_COMP_TIME_WIDTH(15) |
+ SOLO_DCT_INTERVAL(solo_dev->type == SOLO_DEV_6010 ? 9 : 10));
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE(i),
+ (SOLO_EREF_EXT_ADDR(solo_dev) +
+ (i * SOLO_EREF_EXT_SIZE)) >> 16);
+ solo_reg_write(solo_dev, SOLO_VE_CH_REF_BASE_E(i),
+ (SOLO_EREF_EXT_ADDR(solo_dev) +
+ ((i + 16) * SOLO_EREF_EXT_SIZE)) >> 16);
+ }
+
+ if (solo_dev->type == SOLO_DEV_6110) {
+ solo_reg_write(solo_dev, SOLO_VE_COMPT_MOT, 0x00040008);
+ } else {
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_reg_write(solo_dev, SOLO_VE_CH_MOT(i), 0x100);
+ }
+}
+
+int solo_enc_init(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_capture_config(solo_dev);
+ solo_mp4e_config(solo_dev);
+ solo_jpeg_config(solo_dev);
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0);
+ }
+
+ return 0;
+}
+
+void solo_enc_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(i), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(i), 0);
+ }
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-g723.c b/drivers/media/pci/solo6x10/solo6x10-g723.c
new file mode 100644
index 000000000..6cebad665
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-g723.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/mempool.h>
+#include <linux/poll.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+#define G723_FDMA_PAGES 32
+#define G723_PERIOD_BYTES 48
+#define G723_PERIOD_BLOCK 1024
+#define G723_FRAMES_PER_PAGE 48
+
+/* Sets up channels 16-19 for decoding and 0-15 for encoding */
+#define OUTMODE_MASK 0x300
+
+#define SAMPLERATE 8000
+#define BITRATE 25
+
+/* The solo writes to 1k byte pages, 32 pages, in the dma. Each 1k page
+ * is broken down to 20 * 48 byte regions (one for each channel possible)
+ * with the rest of the page being dummy data. */
+#define PERIODS G723_FDMA_PAGES
+#define G723_INTR_ORDER 4 /* 0 - 4 */
+
+struct solo_snd_pcm {
+ int on;
+ spinlock_t lock;
+ struct solo_dev *solo_dev;
+ u8 *g723_buf;
+ dma_addr_t g723_dma;
+};
+
+static void solo_g723_config(struct solo_dev *solo_dev)
+{
+ int clk_div;
+
+ clk_div = (solo_dev->clock_mhz * 1000000)
+ / (SAMPLERATE * (BITRATE * 2) * 2);
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_SAMPLE,
+ SOLO_AUDIO_BITRATE(BITRATE)
+ | SOLO_AUDIO_CLK_DIV(clk_div));
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_FDMA_INTR,
+ SOLO_AUDIO_FDMA_INTERVAL(1)
+ | SOLO_AUDIO_INTR_ORDER(G723_INTR_ORDER)
+ | SOLO_AUDIO_FDMA_BASE(SOLO_G723_EXT_ADDR(solo_dev) >> 16));
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL,
+ SOLO_AUDIO_ENABLE
+ | SOLO_AUDIO_I2S_MODE
+ | SOLO_AUDIO_I2S_MULTI(3)
+ | SOLO_AUDIO_MODE(OUTMODE_MASK));
+}
+
+void solo_g723_isr(struct solo_dev *solo_dev)
+{
+ struct snd_pcm_str *pstr =
+ &solo_dev->snd_pcm->streams[SNDRV_PCM_STREAM_CAPTURE];
+ struct snd_pcm_substream *ss;
+ struct solo_snd_pcm *solo_pcm;
+
+ for (ss = pstr->substream; ss != NULL; ss = ss->next) {
+ if (snd_pcm_substream_chip(ss) == NULL)
+ continue;
+
+ /* This means open() hasn't been called on this one */
+ if (snd_pcm_substream_chip(ss) == solo_dev)
+ continue;
+
+ /* Haven't triggered a start yet */
+ solo_pcm = snd_pcm_substream_chip(ss);
+ if (!solo_pcm->on)
+ continue;
+
+ snd_pcm_period_elapsed(ss);
+ }
+}
+
+static const struct snd_pcm_hardware snd_solo_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8,
+ .rates = SNDRV_PCM_RATE_8000,
+ .rate_min = SAMPLERATE,
+ .rate_max = SAMPLERATE,
+ .channels_min = 1,
+ .channels_max = 1,
+ .buffer_bytes_max = G723_PERIOD_BYTES * PERIODS,
+ .period_bytes_min = G723_PERIOD_BYTES,
+ .period_bytes_max = G723_PERIOD_BYTES,
+ .periods_min = PERIODS,
+ .periods_max = PERIODS,
+};
+
+static int snd_solo_pcm_open(struct snd_pcm_substream *ss)
+{
+ struct solo_dev *solo_dev = snd_pcm_substream_chip(ss);
+ struct solo_snd_pcm *solo_pcm;
+
+ solo_pcm = kzalloc(sizeof(*solo_pcm), GFP_KERNEL);
+ if (solo_pcm == NULL)
+ goto oom;
+
+ solo_pcm->g723_buf = dma_alloc_coherent(&solo_dev->pdev->dev,
+ G723_PERIOD_BYTES,
+ &solo_pcm->g723_dma,
+ GFP_KERNEL);
+ if (solo_pcm->g723_buf == NULL)
+ goto oom;
+
+ spin_lock_init(&solo_pcm->lock);
+ solo_pcm->solo_dev = solo_dev;
+ ss->runtime->hw = snd_solo_pcm_hw;
+
+ snd_pcm_substream_chip(ss) = solo_pcm;
+
+ return 0;
+
+oom:
+ kfree(solo_pcm);
+ return -ENOMEM;
+}
+
+static int snd_solo_pcm_close(struct snd_pcm_substream *ss)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+
+ snd_pcm_substream_chip(ss) = solo_pcm->solo_dev;
+ dma_free_coherent(&solo_pcm->solo_dev->pdev->dev, G723_PERIOD_BYTES,
+ solo_pcm->g723_buf, solo_pcm->g723_dma);
+ kfree(solo_pcm);
+
+ return 0;
+}
+
+static int snd_solo_pcm_trigger(struct snd_pcm_substream *ss, int cmd)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ int ret = 0;
+
+ spin_lock(&solo_pcm->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (solo_pcm->on == 0) {
+ /* If this is the first user, switch on interrupts */
+ if (atomic_inc_return(&solo_dev->snd_users) == 1)
+ solo_irq_on(solo_dev, SOLO_IRQ_G723);
+ solo_pcm->on = 1;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ if (solo_pcm->on) {
+ /* If this was our last user, switch them off */
+ if (atomic_dec_return(&solo_dev->snd_users) == 0)
+ solo_irq_off(solo_dev, SOLO_IRQ_G723);
+ solo_pcm->on = 0;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ spin_unlock(&solo_pcm->lock);
+
+ return ret;
+}
+
+static int snd_solo_pcm_prepare(struct snd_pcm_substream *ss)
+{
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_solo_pcm_pointer(struct snd_pcm_substream *ss)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ snd_pcm_uframes_t idx = solo_reg_read(solo_dev, SOLO_AUDIO_STA) & 0x1f;
+
+ return idx * G723_FRAMES_PER_PAGE;
+}
+
+static int snd_solo_pcm_copy_user(struct snd_pcm_substream *ss, int channel,
+ unsigned long pos, void __user *dst,
+ unsigned long count)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ int err, i;
+
+ for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) {
+ int page = (pos / G723_FRAMES_PER_PAGE) + i;
+
+ err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma,
+ SOLO_G723_EXT_ADDR(solo_dev) +
+ (page * G723_PERIOD_BLOCK) +
+ (ss->number * G723_PERIOD_BYTES),
+ G723_PERIOD_BYTES, 0, 0);
+ if (err)
+ return err;
+
+ if (copy_to_user(dst, solo_pcm->g723_buf, G723_PERIOD_BYTES))
+ return -EFAULT;
+ dst += G723_PERIOD_BYTES;
+ }
+
+ return 0;
+}
+
+static int snd_solo_pcm_copy_kernel(struct snd_pcm_substream *ss, int channel,
+ unsigned long pos, void *dst,
+ unsigned long count)
+{
+ struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+ struct solo_dev *solo_dev = solo_pcm->solo_dev;
+ int err, i;
+
+ for (i = 0; i < (count / G723_FRAMES_PER_PAGE); i++) {
+ int page = (pos / G723_FRAMES_PER_PAGE) + i;
+
+ err = solo_p2m_dma_t(solo_dev, 0, solo_pcm->g723_dma,
+ SOLO_G723_EXT_ADDR(solo_dev) +
+ (page * G723_PERIOD_BLOCK) +
+ (ss->number * G723_PERIOD_BYTES),
+ G723_PERIOD_BYTES, 0, 0);
+ if (err)
+ return err;
+
+ memcpy(dst, solo_pcm->g723_buf, G723_PERIOD_BYTES);
+ dst += G723_PERIOD_BYTES;
+ }
+
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_solo_pcm_ops = {
+ .open = snd_solo_pcm_open,
+ .close = snd_solo_pcm_close,
+ .prepare = snd_solo_pcm_prepare,
+ .trigger = snd_solo_pcm_trigger,
+ .pointer = snd_solo_pcm_pointer,
+ .copy_user = snd_solo_pcm_copy_user,
+ .copy_kernel = snd_solo_pcm_copy_kernel,
+};
+
+static int snd_solo_capture_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *info)
+{
+ info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ info->count = 1;
+ info->value.integer.min = 0;
+ info->value.integer.max = 15;
+ info->value.integer.step = 1;
+
+ return 0;
+}
+
+static int snd_solo_capture_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
+ u8 ch = value->id.numid - 1;
+
+ value->value.integer.value[0] = tw28_get_audio_gain(solo_dev, ch);
+
+ return 0;
+}
+
+static int snd_solo_capture_volume_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *value)
+{
+ struct solo_dev *solo_dev = snd_kcontrol_chip(kcontrol);
+ u8 ch = value->id.numid - 1;
+ u8 old_val;
+
+ old_val = tw28_get_audio_gain(solo_dev, ch);
+ if (old_val == value->value.integer.value[0])
+ return 0;
+
+ tw28_set_audio_gain(solo_dev, ch, value->value.integer.value[0]);
+
+ return 1;
+}
+
+static const struct snd_kcontrol_new snd_solo_capture_volume = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Volume",
+ .info = snd_solo_capture_volume_info,
+ .get = snd_solo_capture_volume_get,
+ .put = snd_solo_capture_volume_put,
+};
+
+static int solo_snd_pcm_init(struct solo_dev *solo_dev)
+{
+ struct snd_card *card = solo_dev->snd_card;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *ss;
+ int ret;
+ int i;
+
+ ret = snd_pcm_new(card, card->driver, 0, 0, solo_dev->nr_chans,
+ &pcm);
+ if (ret < 0)
+ return ret;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_solo_pcm_ops);
+
+ snd_pcm_chip(pcm) = solo_dev;
+ pcm->info_flags = 0;
+ strscpy(pcm->name, card->shortname, sizeof(pcm->name));
+
+ for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+ ss; ss = ss->next, i++)
+ sprintf(ss->name, "Camera #%d Audio", i);
+
+ snd_pcm_set_managed_buffer_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ NULL,
+ G723_PERIOD_BYTES * PERIODS,
+ G723_PERIOD_BYTES * PERIODS);
+
+ solo_dev->snd_pcm = pcm;
+
+ return 0;
+}
+
+int solo_g723_init(struct solo_dev *solo_dev)
+{
+ static struct snd_device_ops ops = { };
+ struct snd_card *card;
+ struct snd_kcontrol_new kctl;
+ char name[32];
+ int ret;
+
+ atomic_set(&solo_dev->snd_users, 0);
+
+ /* Allows for easier mapping between video and audio */
+ sprintf(name, "Softlogic%d", solo_dev->vfd->num);
+
+ ret = snd_card_new(&solo_dev->pdev->dev,
+ SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0,
+ &solo_dev->snd_card);
+ if (ret < 0)
+ return ret;
+
+ card = solo_dev->snd_card;
+
+ strscpy(card->driver, SOLO6X10_NAME, sizeof(card->driver));
+ strscpy(card->shortname, "SOLO-6x10 Audio", sizeof(card->shortname));
+ sprintf(card->longname, "%s on %s IRQ %d", card->shortname,
+ pci_name(solo_dev->pdev), solo_dev->pdev->irq);
+
+ ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops);
+ if (ret < 0)
+ goto snd_error;
+
+ /* Mixer controls */
+ strscpy(card->mixername, "SOLO-6x10", sizeof(card->mixername));
+ kctl = snd_solo_capture_volume;
+ kctl.count = solo_dev->nr_chans;
+
+ ret = snd_ctl_add(card, snd_ctl_new1(&kctl, solo_dev));
+ if (ret < 0)
+ goto snd_error;
+
+ ret = solo_snd_pcm_init(solo_dev);
+ if (ret < 0)
+ goto snd_error;
+
+ ret = snd_card_register(card);
+ if (ret < 0)
+ goto snd_error;
+
+ solo_g723_config(solo_dev);
+
+ dev_info(&solo_dev->pdev->dev, "Alsa sound card as %s\n", name);
+
+ return 0;
+
+snd_error:
+ snd_card_free(card);
+ return ret;
+}
+
+void solo_g723_exit(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->snd_card)
+ return;
+
+ solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0);
+ solo_irq_off(solo_dev, SOLO_IRQ_G723);
+
+ snd_card_free(solo_dev->snd_card);
+ solo_dev->snd_card = NULL;
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-gpio.c b/drivers/media/pci/solo6x10/solo6x10-gpio.c
new file mode 100644
index 000000000..084c30760
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-gpio.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+
+#include "solo6x10.h"
+
+static void solo_gpio_mode(struct solo_dev *solo_dev,
+ unsigned int port_mask, unsigned int mode)
+{
+ int port;
+ unsigned int ret;
+
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0);
+
+ /* To set gpio */
+ for (port = 0; port < 16; port++) {
+ if (!((1 << port) & port_mask))
+ continue;
+
+ ret &= (~(3 << (port << 1)));
+ ret |= ((mode & 3) << (port << 1));
+ }
+
+ solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_0, ret);
+
+ /* To set extended gpio - sensor */
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1);
+
+ for (port = 0; port < 16; port++) {
+ if (!((1UL << (port + 16)) & port_mask))
+ continue;
+
+ if (!mode)
+ ret &= ~(1UL << port);
+ else
+ ret |= 1UL << port;
+ }
+
+ /* Enable GPIO[31:16] */
+ ret |= 0xffff0000;
+
+ solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_1, ret);
+}
+
+static void solo_gpio_set(struct solo_dev *solo_dev, unsigned int value)
+{
+ solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT,
+ solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) | value);
+}
+
+static void solo_gpio_clear(struct solo_dev *solo_dev, unsigned int value)
+{
+ solo_reg_write(solo_dev, SOLO_GPIO_DATA_OUT,
+ solo_reg_read(solo_dev, SOLO_GPIO_DATA_OUT) & ~value);
+}
+
+static void solo_gpio_config(struct solo_dev *solo_dev)
+{
+ /* Video reset */
+ solo_gpio_mode(solo_dev, 0x30, 1);
+ solo_gpio_clear(solo_dev, 0x30);
+ udelay(100);
+ solo_gpio_set(solo_dev, 0x30);
+ udelay(100);
+
+ /* Warning: Don't touch the next line unless you're sure of what
+ * you're doing: first four gpio [0-3] are used for video. */
+ solo_gpio_mode(solo_dev, 0x0f, 2);
+
+ /* We use bit 8-15 of SOLO_GPIO_CONFIG_0 for relay purposes */
+ solo_gpio_mode(solo_dev, 0xff00, 1);
+
+ /* Initially set relay status to 0 */
+ solo_gpio_clear(solo_dev, 0xff00);
+
+ /* Set input pins direction */
+ solo_gpio_mode(solo_dev, 0xffff0000, 0);
+}
+
+#ifdef CONFIG_GPIOLIB
+/* Pins 0-7 are not exported, because it seems from code above they are
+ * used for internal purposes. So offset 0 corresponds to pin 8, therefore
+ * offsets 0-7 are relay GPIOs, 8-23 - input GPIOs.
+ */
+static int solo_gpiochip_get_direction(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret, mode;
+ struct solo_dev *solo_dev = gpiochip_get_data(chip);
+
+ if (offset < 8) {
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_0);
+ mode = 3 & (ret >> ((offset + 8) * 2));
+ } else {
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_CONFIG_1);
+ mode = 1 & (ret >> (offset - 8));
+ }
+
+ if (!mode)
+ return 1;
+ else if (mode == 1)
+ return 0;
+
+ return -1;
+}
+
+static int solo_gpiochip_direction_input(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ return -1;
+}
+
+static int solo_gpiochip_direction_output(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ return -1;
+}
+
+static int solo_gpiochip_get(struct gpio_chip *chip,
+ unsigned int offset)
+{
+ int ret;
+ struct solo_dev *solo_dev = gpiochip_get_data(chip);
+
+ ret = solo_reg_read(solo_dev, SOLO_GPIO_DATA_IN);
+
+ return 1 & (ret >> (offset + 8));
+}
+
+static void solo_gpiochip_set(struct gpio_chip *chip,
+ unsigned int offset, int value)
+{
+ struct solo_dev *solo_dev = gpiochip_get_data(chip);
+
+ if (value)
+ solo_gpio_set(solo_dev, 1 << (offset + 8));
+ else
+ solo_gpio_clear(solo_dev, 1 << (offset + 8));
+}
+#endif
+
+int solo_gpio_init(struct solo_dev *solo_dev)
+{
+#ifdef CONFIG_GPIOLIB
+ int ret;
+#endif
+
+ solo_gpio_config(solo_dev);
+#ifdef CONFIG_GPIOLIB
+ solo_dev->gpio_dev.label = SOLO6X10_NAME"_gpio";
+ solo_dev->gpio_dev.parent = &solo_dev->pdev->dev;
+ solo_dev->gpio_dev.owner = THIS_MODULE;
+ solo_dev->gpio_dev.base = -1;
+ solo_dev->gpio_dev.ngpio = 24;
+ solo_dev->gpio_dev.can_sleep = 0;
+
+ solo_dev->gpio_dev.get_direction = solo_gpiochip_get_direction;
+ solo_dev->gpio_dev.direction_input = solo_gpiochip_direction_input;
+ solo_dev->gpio_dev.direction_output = solo_gpiochip_direction_output;
+ solo_dev->gpio_dev.get = solo_gpiochip_get;
+ solo_dev->gpio_dev.set = solo_gpiochip_set;
+
+ ret = gpiochip_add_data(&solo_dev->gpio_dev, solo_dev);
+
+ if (ret) {
+ solo_dev->gpio_dev.label = NULL;
+ return -1;
+ }
+#endif
+ return 0;
+}
+
+void solo_gpio_exit(struct solo_dev *solo_dev)
+{
+#ifdef CONFIG_GPIOLIB
+ if (solo_dev->gpio_dev.label) {
+ gpiochip_remove(&solo_dev->gpio_dev);
+ solo_dev->gpio_dev.label = NULL;
+ }
+#endif
+ solo_gpio_clear(solo_dev, 0x30);
+ solo_gpio_config(solo_dev);
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-i2c.c b/drivers/media/pci/solo6x10/solo6x10-i2c.c
new file mode 100644
index 000000000..7db785e9c
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-i2c.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+/* XXX: The SOLO6x10 i2c does not have separate interrupts for each i2c
+ * channel. The bus can only handle one i2c event at a time. The below handles
+ * this all wrong. We should be using the status registers to see if the bus
+ * is in use, and have a global lock to check the status register. Also,
+ * the bulk of the work should be handled out-of-interrupt. The ugly loops
+ * that occur during interrupt scare me. The ISR should merely signal
+ * thread context, ACK the interrupt, and move on. -- BenC */
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+
+#include "solo6x10.h"
+
+u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off)
+{
+ struct i2c_msg msgs[2];
+ u8 data;
+
+ msgs[0].flags = 0;
+ msgs[0].addr = addr;
+ msgs[0].len = 1;
+ msgs[0].buf = &off;
+
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].addr = addr;
+ msgs[1].len = 1;
+ msgs[1].buf = &data;
+
+ i2c_transfer(&solo_dev->i2c_adap[id], msgs, 2);
+
+ return data;
+}
+
+void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr,
+ u8 off, u8 data)
+{
+ struct i2c_msg msgs;
+ u8 buf[2];
+
+ buf[0] = off;
+ buf[1] = data;
+ msgs.flags = 0;
+ msgs.addr = addr;
+ msgs.len = 2;
+ msgs.buf = buf;
+
+ i2c_transfer(&solo_dev->i2c_adap[id], &msgs, 1);
+}
+
+static void solo_i2c_flush(struct solo_dev *solo_dev, int wr)
+{
+ u32 ctrl;
+
+ ctrl = SOLO_IIC_CH_SET(solo_dev->i2c_id);
+
+ if (solo_dev->i2c_state == IIC_STATE_START)
+ ctrl |= SOLO_IIC_START;
+
+ if (wr) {
+ ctrl |= SOLO_IIC_WRITE;
+ } else {
+ ctrl |= SOLO_IIC_READ;
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NO_RD_ACK))
+ ctrl |= SOLO_IIC_ACK_EN;
+ }
+
+ if (solo_dev->i2c_msg_ptr == solo_dev->i2c_msg->len)
+ ctrl |= SOLO_IIC_STOP;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, ctrl);
+}
+
+static void solo_i2c_start(struct solo_dev *solo_dev)
+{
+ u32 addr = solo_dev->i2c_msg->addr << 1;
+
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ addr |= 1;
+
+ solo_dev->i2c_state = IIC_STATE_START;
+ solo_reg_write(solo_dev, SOLO_IIC_TXD, addr);
+ solo_i2c_flush(solo_dev, 1);
+}
+
+static void solo_i2c_stop(struct solo_dev *solo_dev)
+{
+ solo_irq_off(solo_dev, SOLO_IRQ_IIC);
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
+ solo_dev->i2c_state = IIC_STATE_STOP;
+ wake_up(&solo_dev->i2c_wait);
+}
+
+static int solo_i2c_handle_read(struct solo_dev *solo_dev)
+{
+prepare_read:
+ if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
+ solo_i2c_flush(solo_dev, 0);
+ return 0;
+ }
+
+ solo_dev->i2c_msg_ptr = 0;
+ solo_dev->i2c_msg++;
+ solo_dev->i2c_msg_num--;
+
+ if (solo_dev->i2c_msg_num == 0) {
+ solo_i2c_stop(solo_dev);
+ return 0;
+ }
+
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
+ solo_i2c_start(solo_dev);
+ } else {
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ goto prepare_read;
+ else
+ solo_i2c_stop(solo_dev);
+ }
+
+ return 0;
+}
+
+static int solo_i2c_handle_write(struct solo_dev *solo_dev)
+{
+retry_write:
+ if (solo_dev->i2c_msg_ptr != solo_dev->i2c_msg->len) {
+ solo_reg_write(solo_dev, SOLO_IIC_TXD,
+ solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr]);
+ solo_dev->i2c_msg_ptr++;
+ solo_i2c_flush(solo_dev, 1);
+ return 0;
+ }
+
+ solo_dev->i2c_msg_ptr = 0;
+ solo_dev->i2c_msg++;
+ solo_dev->i2c_msg_num--;
+
+ if (solo_dev->i2c_msg_num == 0) {
+ solo_i2c_stop(solo_dev);
+ return 0;
+ }
+
+ if (!(solo_dev->i2c_msg->flags & I2C_M_NOSTART)) {
+ solo_i2c_start(solo_dev);
+ } else {
+ if (solo_dev->i2c_msg->flags & I2C_M_RD)
+ solo_i2c_stop(solo_dev);
+ else
+ goto retry_write;
+ }
+
+ return 0;
+}
+
+int solo_i2c_isr(struct solo_dev *solo_dev)
+{
+ u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL);
+ int ret = -EINVAL;
+
+
+ if (CHK_FLAGS(status, SOLO_IIC_STATE_TRNS | SOLO_IIC_STATE_SIG_ERR)
+ || solo_dev->i2c_id < 0) {
+ solo_i2c_stop(solo_dev);
+ return -ENXIO;
+ }
+
+ switch (solo_dev->i2c_state) {
+ case IIC_STATE_START:
+ if (solo_dev->i2c_msg->flags & I2C_M_RD) {
+ solo_dev->i2c_state = IIC_STATE_READ;
+ ret = solo_i2c_handle_read(solo_dev);
+ break;
+ }
+
+ solo_dev->i2c_state = IIC_STATE_WRITE;
+ fallthrough;
+ case IIC_STATE_WRITE:
+ ret = solo_i2c_handle_write(solo_dev);
+ break;
+
+ case IIC_STATE_READ:
+ solo_dev->i2c_msg->buf[solo_dev->i2c_msg_ptr] =
+ solo_reg_read(solo_dev, SOLO_IIC_RXD);
+ solo_dev->i2c_msg_ptr++;
+
+ ret = solo_i2c_handle_read(solo_dev);
+ break;
+
+ default:
+ solo_i2c_stop(solo_dev);
+ }
+
+ return ret;
+}
+
+static int solo_i2c_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ struct solo_dev *solo_dev = adap->algo_data;
+ unsigned long timeout;
+ int ret;
+ int i;
+ DEFINE_WAIT(wait);
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (&solo_dev->i2c_adap[i] == adap)
+ break;
+ }
+
+ if (i == SOLO_I2C_ADAPTERS)
+ return num; /* XXX Right return value for failure? */
+
+ mutex_lock(&solo_dev->i2c_mutex);
+ solo_dev->i2c_id = i;
+ solo_dev->i2c_msg = msgs;
+ solo_dev->i2c_msg_num = num;
+ solo_dev->i2c_msg_ptr = 0;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CTRL, 0);
+ solo_irq_on(solo_dev, SOLO_IRQ_IIC);
+ solo_i2c_start(solo_dev);
+
+ timeout = HZ / 2;
+
+ for (;;) {
+ prepare_to_wait(&solo_dev->i2c_wait, &wait,
+ TASK_INTERRUPTIBLE);
+
+ if (solo_dev->i2c_state == IIC_STATE_STOP)
+ break;
+
+ timeout = schedule_timeout(timeout);
+ if (!timeout)
+ break;
+
+ if (signal_pending(current))
+ break;
+ }
+
+ finish_wait(&solo_dev->i2c_wait, &wait);
+ ret = num - solo_dev->i2c_msg_num;
+ solo_dev->i2c_state = IIC_STATE_IDLE;
+ solo_dev->i2c_id = -1;
+
+ mutex_unlock(&solo_dev->i2c_mutex);
+
+ return ret;
+}
+
+static u32 solo_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm solo_i2c_algo = {
+ .master_xfer = solo_i2c_master_xfer,
+ .functionality = solo_i2c_functionality,
+};
+
+int solo_i2c_init(struct solo_dev *solo_dev)
+{
+ int i;
+ int ret;
+
+ solo_reg_write(solo_dev, SOLO_IIC_CFG,
+ SOLO_IIC_PRESCALE(8) | SOLO_IIC_ENABLE);
+
+ solo_dev->i2c_id = -1;
+ solo_dev->i2c_state = IIC_STATE_IDLE;
+ init_waitqueue_head(&solo_dev->i2c_wait);
+ mutex_init(&solo_dev->i2c_mutex);
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ struct i2c_adapter *adap = &solo_dev->i2c_adap[i];
+
+ snprintf(adap->name, I2C_NAME_SIZE, "%s I2C %d",
+ SOLO6X10_NAME, i);
+ adap->algo = &solo_i2c_algo;
+ adap->algo_data = solo_dev;
+ adap->retries = 1;
+ adap->dev.parent = &solo_dev->pdev->dev;
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ adap->algo_data = NULL;
+ break;
+ }
+ }
+
+ if (ret) {
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (!solo_dev->i2c_adap[i].algo_data)
+ break;
+ i2c_del_adapter(&solo_dev->i2c_adap[i]);
+ solo_dev->i2c_adap[i].algo_data = NULL;
+ }
+ return ret;
+ }
+
+ return 0;
+}
+
+void solo_i2c_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < SOLO_I2C_ADAPTERS; i++) {
+ if (!solo_dev->i2c_adap[i].algo_data)
+ continue;
+ i2c_del_adapter(&solo_dev->i2c_adap[i]);
+ solo_dev->i2c_adap[i].algo_data = NULL;
+ }
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-jpeg.h b/drivers/media/pci/solo6x10/solo6x10-jpeg.h
new file mode 100644
index 000000000..e212f4828
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-jpeg.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#ifndef __SOLO6X10_JPEG_H
+#define __SOLO6X10_JPEG_H
+
+static const u8 jpeg_header[] = {
+ 0xff, 0xd8, 0xff, 0xfe, 0x00, 0x0d, 0x42, 0x6c,
+ 0x75, 0x65, 0x63, 0x68, 0x65, 0x72, 0x72, 0x79,
+ 0x20, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x20, 0x16,
+ 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+ 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30,
+ 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a, 0x3a, 0x50,
+ 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66, 0x70, 0x6e,
+ 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a,
+ 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4,
+ 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2, 0xf2, 0xe0,
+ 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0xff, 0xdb,
+ 0x00, 0x43, 0x01, 0x22, 0x24, 0x24, 0x30, 0x2a,
+ 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6, 0x84, 0x70,
+ 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01,
+ 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04,
+ 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03,
+ 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41,
+ 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14,
+ 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
+ 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62,
+ 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
+ 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84,
+ 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+ 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2,
+ 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+ 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9,
+ 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+ 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+ 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5,
+ 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01,
+ 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01,
+ 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04,
+ 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02,
+ 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+ 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32,
+ 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
+ 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+ 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1,
+ 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43,
+ 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53,
+ 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63,
+ 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73,
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82,
+ 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+ 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
+ 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
+ 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4,
+ 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff,
+ 0xc0, 0x00, 0x11, 0x08, 0x00, 0xf0, 0x02, 0xc0,
+ 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03,
+ 0x11, 0x01, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01,
+ 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00
+};
+
+/* This is the byte marker for the start of SOF0: 0xffc0 marker */
+#define SOF0_START 575
+
+/* This is the byte marker for the start of the DQT */
+#define DQT_START 17
+#define DQT_LEN 138
+static const u8 jpeg_dqt[4][DQT_LEN] = {
+ {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07,
+ 0x07, 0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14,
+ 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13,
+ 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a,
+ 0x1c, 0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22,
+ 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c,
+ 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39,
+ 0x3d, 0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0d,
+ 0x0d, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e,
+ 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
+ 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
+ 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33,
+ 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44,
+ 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57,
+ 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71,
+ 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+ 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c,
+ 0x1a, 0x1c, 0x24, 0x22, 0x20, 0x26, 0x30, 0x50,
+ 0x34, 0x30, 0x2c, 0x2c, 0x30, 0x62, 0x46, 0x4a,
+ 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+ 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88,
+ 0xae, 0x8a, 0x6e, 0x70, 0xa0, 0xda, 0xa2, 0xae,
+ 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c, 0x9a, 0xe2,
+ 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34,
+ 0x34, 0x5e, 0xc6, 0x84, 0x70, 0x84, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6
+ }, {
+ 0xff, 0xdb, 0x00, 0x43, 0x00,
+ 0x30, 0x21, 0x24, 0x2a, 0x24, 0x1e, 0x30, 0x2a,
+ 0x27, 0x2a, 0x36, 0x33, 0x30, 0x39, 0x48, 0x78,
+ 0x4e, 0x48, 0x42, 0x42, 0x48, 0x93, 0x69, 0x6f,
+ 0x57, 0x78, 0xae, 0x99, 0xb7, 0xb4, 0xab, 0x99,
+ 0xa8, 0xa5, 0xc0, 0xd8, 0xff, 0xea, 0xc0, 0xcc,
+ 0xff, 0xcf, 0xa5, 0xa8, 0xf0, 0xff, 0xf3, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xba, 0xe7, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xdb, 0x00, 0x43, 0x01,
+ 0x33, 0x36, 0x36, 0x48, 0x3f, 0x48, 0x8d, 0x4e,
+ 0x4e, 0x8d, 0xff, 0xc6, 0xa8, 0xc6, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ }
+};
+
+#endif /* __SOLO6X10_JPEG_H */
diff --git a/drivers/media/pci/solo6x10/solo6x10-offsets.h b/drivers/media/pci/solo6x10/solo6x10-offsets.h
new file mode 100644
index 000000000..f414ee131
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-offsets.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#ifndef __SOLO6X10_OFFSETS_H
+#define __SOLO6X10_OFFSETS_H
+
+#define SOLO_DISP_EXT_ADDR 0x00000000
+#define SOLO_DISP_EXT_SIZE 0x00480000
+
+#define SOLO_EOSD_EXT_ADDR \
+ (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE)
+#define SOLO_EOSD_EXT_SIZE(__solo) \
+ (__solo->type == SOLO_DEV_6010 ? 0x10000 : 0x20000)
+#define SOLO_EOSD_EXT_SIZE_MAX 0x20000
+#define SOLO_EOSD_EXT_AREA(__solo) \
+ (SOLO_EOSD_EXT_SIZE(__solo) * 32)
+#define SOLO_EOSD_EXT_ADDR_CHAN(__solo, ch) \
+ (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_SIZE(__solo) * (ch))
+
+#define SOLO_MOTION_EXT_ADDR(__solo) \
+ (SOLO_EOSD_EXT_ADDR + SOLO_EOSD_EXT_AREA(__solo))
+#define SOLO_MOTION_EXT_SIZE 0x00080000
+
+#define SOLO_G723_EXT_ADDR(__solo) \
+ (SOLO_MOTION_EXT_ADDR(__solo) + SOLO_MOTION_EXT_SIZE)
+#define SOLO_G723_EXT_SIZE 0x00010000
+
+#define SOLO_CAP_EXT_ADDR(__solo) \
+ (SOLO_G723_EXT_ADDR(__solo) + SOLO_G723_EXT_SIZE)
+
+/* 18 is the maximum number of pages required for PAL@D1, the largest frame
+ * possible */
+#define SOLO_CAP_PAGE_SIZE (18 << 16)
+
+/* Always allow the encoder enough for 16 channels, even if we have less. The
+ * exception is if we have card with only 32Megs of memory. */
+#define SOLO_CAP_EXT_SIZE(__solo) \
+ ((((__solo->sdram_size <= (32 << 20)) ? 4 : 16) + 1) \
+ * SOLO_CAP_PAGE_SIZE)
+
+#define SOLO_EREF_EXT_ADDR(__solo) \
+ (SOLO_CAP_EXT_ADDR(__solo) + SOLO_CAP_EXT_SIZE(__solo))
+#define SOLO_EREF_EXT_SIZE 0x00140000
+#define SOLO_EREF_EXT_AREA(__solo) \
+ (SOLO_EREF_EXT_SIZE * __solo->nr_chans * 2)
+
+#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000)
+
+#define SOLO_MP4E_EXT_ADDR(__solo) \
+ (SOLO_EREF_EXT_ADDR(__solo) + SOLO_EREF_EXT_AREA(__solo))
+#define SOLO_MP4E_EXT_SIZE(__solo) \
+ max((__solo->nr_chans * 0x00080000), \
+ min(((__solo->sdram_size - SOLO_MP4E_EXT_ADDR(__solo)) - \
+ __SOLO_JPEG_MIN_SIZE(__solo)), 0x00ff0000))
+
+#define __SOLO_JPEG_MIN_SIZE(__solo) (__solo->nr_chans * 0x00080000)
+#define SOLO_JPEG_EXT_ADDR(__solo) \
+ (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo))
+#define SOLO_JPEG_EXT_SIZE(__solo) \
+ max(__SOLO_JPEG_MIN_SIZE(__solo), \
+ min((__solo->sdram_size - SOLO_JPEG_EXT_ADDR(__solo)), 0x00ff0000))
+
+#define SOLO_SDRAM_END(__solo) \
+ (SOLO_JPEG_EXT_ADDR(__solo) + SOLO_JPEG_EXT_SIZE(__solo))
+
+#endif /* __SOLO6X10_OFFSETS_H */
diff --git a/drivers/media/pci/solo6x10/solo6x10-p2m.c b/drivers/media/pci/solo6x10/solo6x10-p2m.c
new file mode 100644
index 000000000..ca70a864a
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-p2m.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "solo6x10.h"
+
+static int multi_p2m;
+module_param(multi_p2m, uint, 0644);
+MODULE_PARM_DESC(multi_p2m,
+ "Use multiple P2M DMA channels (default: no, 6010-only)");
+
+static int desc_mode;
+module_param(desc_mode, uint, 0644);
+MODULE_PARM_DESC(desc_mode,
+ "Allow use of descriptor mode DMA (default: no, 6010-only)");
+
+int solo_p2m_dma(struct solo_dev *solo_dev, int wr,
+ void *sys_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ dma_addr_t dma_addr;
+ int ret;
+
+ if (WARN_ON_ONCE((unsigned long)sys_addr & 0x03))
+ return -EINVAL;
+ if (WARN_ON_ONCE(!size))
+ return -EINVAL;
+
+ dma_addr = dma_map_single(&solo_dev->pdev->dev, sys_addr, size,
+ wr ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ if (dma_mapping_error(&solo_dev->pdev->dev, dma_addr))
+ return -ENOMEM;
+
+ ret = solo_p2m_dma_t(solo_dev, wr, dma_addr, ext_addr, size,
+ repeat, ext_size);
+
+ dma_unmap_single(&solo_dev->pdev->dev, dma_addr, size,
+ wr ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ return ret;
+}
+
+/* Mutex must be held for p2m_id before calling this!! */
+int solo_p2m_dma_desc(struct solo_dev *solo_dev,
+ struct solo_p2m_desc *desc, dma_addr_t desc_dma,
+ int desc_cnt)
+{
+ struct solo_p2m_dev *p2m_dev;
+ unsigned int timeout;
+ unsigned int config = 0;
+ int ret = 0;
+ unsigned int p2m_id = 0;
+
+ /* Get next ID. According to Softlogic, 6110 has problems on !=0 P2M */
+ if (solo_dev->type != SOLO_DEV_6110 && multi_p2m)
+ p2m_id = atomic_inc_return(&solo_dev->p2m_count) % SOLO_NR_P2M;
+
+ p2m_dev = &solo_dev->p2m_dev[p2m_id];
+
+ if (mutex_lock_interruptible(&p2m_dev->mutex))
+ return -EINTR;
+
+ reinit_completion(&p2m_dev->completion);
+ p2m_dev->error = 0;
+
+ if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && desc_mode) {
+ /* For 6010 with more than one desc, we can do a one-shot */
+ p2m_dev->desc_count = p2m_dev->desc_idx = 0;
+ config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(p2m_id));
+
+ solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(p2m_id), desc_dma);
+ solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(p2m_id), desc_cnt);
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config |
+ SOLO_P2M_DESC_MODE);
+ } else {
+ /* For single descriptors and 6110, we need to run each desc */
+ p2m_dev->desc_count = desc_cnt;
+ p2m_dev->desc_idx = 1;
+ p2m_dev->descs = desc;
+
+ solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(p2m_id),
+ desc[1].dma_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(p2m_id),
+ desc[1].ext_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(p2m_id),
+ desc[1].cfg);
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id),
+ desc[1].ctrl);
+ }
+
+ timeout = wait_for_completion_timeout(&p2m_dev->completion,
+ solo_dev->p2m_jiffies);
+
+ if (WARN_ON_ONCE(p2m_dev->error))
+ ret = -EIO;
+ else if (timeout == 0) {
+ solo_dev->p2m_timeouts++;
+ ret = -EAGAIN;
+ }
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(p2m_id), 0);
+
+ /* Don't write here for the no_desc_mode case, because config is 0.
+ * We can't test no_desc_mode again, it might race. */
+ if (desc_cnt > 1 && solo_dev->type != SOLO_DEV_6110 && config)
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(p2m_id), config);
+
+ mutex_unlock(&p2m_dev->mutex);
+
+ return ret;
+}
+
+void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ WARN_ON_ONCE(dma_addr & 0x03);
+ WARN_ON_ONCE(!size);
+
+ desc->cfg = SOLO_P2M_COPY_SIZE(size >> 2);
+ desc->ctrl = SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) |
+ (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON;
+
+ if (repeat) {
+ desc->cfg |= SOLO_P2M_EXT_INC(ext_size >> 2);
+ desc->ctrl |= SOLO_P2M_PCI_INC(size >> 2) |
+ SOLO_P2M_REPEAT(repeat);
+ }
+
+ desc->dma_addr = dma_addr;
+ desc->ext_addr = ext_addr;
+}
+
+int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size)
+{
+ struct solo_p2m_desc desc[2];
+
+ solo_p2m_fill_desc(&desc[1], wr, dma_addr, ext_addr, size, repeat,
+ ext_size);
+
+ /* No need for desc_dma since we know it is a single-shot */
+ return solo_p2m_dma_desc(solo_dev, desc, 0, 1);
+}
+
+void solo_p2m_isr(struct solo_dev *solo_dev, int id)
+{
+ struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id];
+ struct solo_p2m_desc *desc;
+
+ if (p2m_dev->desc_count <= p2m_dev->desc_idx) {
+ complete(&p2m_dev->completion);
+ return;
+ }
+
+ /* Setup next descriptor */
+ p2m_dev->desc_idx++;
+ desc = &p2m_dev->descs[p2m_dev->desc_idx];
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0);
+ solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), desc->dma_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), desc->ext_addr);
+ solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id), desc->cfg);
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), desc->ctrl);
+}
+
+void solo_p2m_error_isr(struct solo_dev *solo_dev)
+{
+ unsigned int err = solo_reg_read(solo_dev, SOLO_PCI_ERR);
+ struct solo_p2m_dev *p2m_dev;
+ int i;
+
+ if (!(err & (SOLO_PCI_ERR_P2M | SOLO_PCI_ERR_P2M_DESC)))
+ return;
+
+ for (i = 0; i < SOLO_NR_P2M; i++) {
+ p2m_dev = &solo_dev->p2m_dev[i];
+ p2m_dev->error = 1;
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0);
+ complete(&p2m_dev->completion);
+ }
+}
+
+void solo_p2m_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ for (i = 0; i < SOLO_NR_P2M; i++)
+ solo_irq_off(solo_dev, SOLO_IRQ_P2M(i));
+}
+
+static int solo_p2m_test(struct solo_dev *solo_dev, int base, int size)
+{
+ u32 *wr_buf;
+ u32 *rd_buf;
+ int i;
+ int ret = -EIO;
+ int order = get_order(size);
+
+ wr_buf = (u32 *)__get_free_pages(GFP_KERNEL, order);
+ if (wr_buf == NULL)
+ return -1;
+
+ rd_buf = (u32 *)__get_free_pages(GFP_KERNEL, order);
+ if (rd_buf == NULL) {
+ free_pages((unsigned long)wr_buf, order);
+ return -1;
+ }
+
+ for (i = 0; i < (size >> 3); i++)
+ *(wr_buf + i) = (i << 16) | (i + 1);
+
+ for (i = (size >> 3); i < (size >> 2); i++)
+ *(wr_buf + i) = ~((i << 16) | (i + 1));
+
+ memset(rd_buf, 0x55, size);
+
+ if (solo_p2m_dma(solo_dev, 1, wr_buf, base, size, 0, 0))
+ goto test_fail;
+
+ if (solo_p2m_dma(solo_dev, 0, rd_buf, base, size, 0, 0))
+ goto test_fail;
+
+ for (i = 0; i < (size >> 2); i++) {
+ if (*(wr_buf + i) != *(rd_buf + i))
+ goto test_fail;
+ }
+
+ ret = 0;
+
+test_fail:
+ free_pages((unsigned long)wr_buf, order);
+ free_pages((unsigned long)rd_buf, order);
+
+ return ret;
+}
+
+int solo_p2m_init(struct solo_dev *solo_dev)
+{
+ struct solo_p2m_dev *p2m_dev;
+ int i;
+
+ for (i = 0; i < SOLO_NR_P2M; i++) {
+ p2m_dev = &solo_dev->p2m_dev[i];
+
+ mutex_init(&p2m_dev->mutex);
+ init_completion(&p2m_dev->completion);
+
+ solo_reg_write(solo_dev, SOLO_P2M_CONTROL(i), 0);
+ solo_reg_write(solo_dev, SOLO_P2M_CONFIG(i),
+ SOLO_P2M_CSC_16BIT_565 |
+ SOLO_P2M_DESC_INTR_OPT |
+ SOLO_P2M_DMA_INTERVAL(0) |
+ SOLO_P2M_PCI_MASTER_MODE);
+ solo_irq_on(solo_dev, SOLO_IRQ_P2M(i));
+ }
+
+ /* Find correct SDRAM size */
+ for (solo_dev->sdram_size = 0, i = 2; i >= 0; i--) {
+ solo_reg_write(solo_dev, SOLO_DMA_CTRL,
+ SOLO_DMA_CTRL_REFRESH_CYCLE(1) |
+ SOLO_DMA_CTRL_SDRAM_SIZE(i) |
+ SOLO_DMA_CTRL_SDRAM_CLK_INVERT |
+ SOLO_DMA_CTRL_READ_CLK_SELECT |
+ SOLO_DMA_CTRL_LATENCY(1));
+
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config |
+ SOLO_SYS_CFG_RESET);
+ solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);
+
+ switch (i) {
+ case 2:
+ if (solo_p2m_test(solo_dev, 0x07ff0000, 0x00010000) ||
+ solo_p2m_test(solo_dev, 0x05ff0000, 0x00010000))
+ continue;
+ break;
+
+ case 1:
+ if (solo_p2m_test(solo_dev, 0x03ff0000, 0x00010000))
+ continue;
+ break;
+
+ default:
+ if (solo_p2m_test(solo_dev, 0x01ff0000, 0x00010000))
+ continue;
+ }
+
+ solo_dev->sdram_size = (32 << 20) << i;
+ break;
+ }
+
+ if (!solo_dev->sdram_size) {
+ dev_err(&solo_dev->pdev->dev, "Error detecting SDRAM size\n");
+ return -EIO;
+ }
+
+ if (SOLO_SDRAM_END(solo_dev) > solo_dev->sdram_size) {
+ dev_err(&solo_dev->pdev->dev,
+ "SDRAM is not large enough (%u < %u)\n",
+ solo_dev->sdram_size, SOLO_SDRAM_END(solo_dev));
+ return -EIO;
+ }
+
+ return 0;
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-regs.h b/drivers/media/pci/solo6x10/solo6x10-regs.h
new file mode 100644
index 000000000..12e0ac190
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-regs.h
@@ -0,0 +1,628 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#ifndef __SOLO6X10_REGISTERS_H
+#define __SOLO6X10_REGISTERS_H
+
+#include <linux/bitops.h>
+
+#include "solo6x10-offsets.h"
+
+/* Global 6010 system configuration */
+#define SOLO_SYS_CFG 0x0000
+#define SOLO_SYS_CFG_FOUT_EN 0x00000001
+#define SOLO_SYS_CFG_PLL_BYPASS 0x00000002
+#define SOLO_SYS_CFG_PLL_PWDN 0x00000004
+#define SOLO_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3)
+#define SOLO_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5)
+#define SOLO_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14)
+#define SOLO_SYS_CFG_CLOCK_DIV 0x00080000
+#define SOLO_SYS_CFG_NCLK_DELAY(__n) (((__n) & 0x003) << 24)
+#define SOLO_SYS_CFG_PCLK_DELAY(__n) (((__n) & 0x00f) << 26)
+#define SOLO_SYS_CFG_SDRAM64BIT 0x40000000
+#define SOLO_SYS_CFG_RESET 0x80000000
+
+#define SOLO_DMA_CTRL 0x0004
+#define SOLO_DMA_CTRL_REFRESH_CYCLE(n) ((n)<<8)
+/* 0=16/32MB, 1=32/64MB, 2=64/128MB, 3=128/256MB */
+#define SOLO_DMA_CTRL_SDRAM_SIZE(n) ((n)<<6)
+#define SOLO_DMA_CTRL_SDRAM_CLK_INVERT BIT(5)
+#define SOLO_DMA_CTRL_STROBE_SELECT BIT(4)
+#define SOLO_DMA_CTRL_READ_DATA_SELECT BIT(3)
+#define SOLO_DMA_CTRL_READ_CLK_SELECT BIT(2)
+#define SOLO_DMA_CTRL_LATENCY(n) ((n)<<0)
+
+/* Some things we set in this are undocumented. Why Softlogic?!?! */
+#define SOLO_DMA_CTRL1 0x0008
+
+#define SOLO_SYS_VCLK 0x000C
+#define SOLO_VCLK_INVERT BIT(22)
+/* 0=sys_clk/4, 1=sys_clk/2, 2=clk_in/2 of system input */
+#define SOLO_VCLK_SELECT(n) ((n)<<20)
+#define SOLO_VCLK_VIN1415_DELAY(n) ((n)<<14)
+#define SOLO_VCLK_VIN1213_DELAY(n) ((n)<<12)
+#define SOLO_VCLK_VIN1011_DELAY(n) ((n)<<10)
+#define SOLO_VCLK_VIN0809_DELAY(n) ((n)<<8)
+#define SOLO_VCLK_VIN0607_DELAY(n) ((n)<<6)
+#define SOLO_VCLK_VIN0405_DELAY(n) ((n)<<4)
+#define SOLO_VCLK_VIN0203_DELAY(n) ((n)<<2)
+#define SOLO_VCLK_VIN0001_DELAY(n) ((n)<<0)
+
+#define SOLO_IRQ_STAT 0x0010
+#define SOLO_IRQ_MASK 0x0014
+#define SOLO_IRQ_P2M(n) BIT((n) + 17)
+#define SOLO_IRQ_GPIO BIT(16)
+#define SOLO_IRQ_VIDEO_LOSS BIT(15)
+#define SOLO_IRQ_VIDEO_IN BIT(14)
+#define SOLO_IRQ_MOTION BIT(13)
+#define SOLO_IRQ_ATA_CMD BIT(12)
+#define SOLO_IRQ_ATA_DIR BIT(11)
+#define SOLO_IRQ_PCI_ERR BIT(10)
+#define SOLO_IRQ_PS2_1 BIT(9)
+#define SOLO_IRQ_PS2_0 BIT(8)
+#define SOLO_IRQ_SPI BIT(7)
+#define SOLO_IRQ_IIC BIT(6)
+#define SOLO_IRQ_UART(n) BIT((n) + 4)
+#define SOLO_IRQ_G723 BIT(3)
+#define SOLO_IRQ_DECODER BIT(1)
+#define SOLO_IRQ_ENCODER BIT(0)
+
+#define SOLO_CHIP_OPTION 0x001C
+#define SOLO_CHIP_ID_MASK 0x00000007
+
+#define SOLO_PLL_CONFIG 0x0020 /* 6110 Only */
+
+#define SOLO_EEPROM_CTRL 0x0060
+#define SOLO_EEPROM_ACCESS_EN BIT(7)
+#define SOLO_EEPROM_CS BIT(3)
+#define SOLO_EEPROM_CLK BIT(2)
+#define SOLO_EEPROM_DO BIT(1)
+#define SOLO_EEPROM_DI BIT(0)
+#define SOLO_EEPROM_ENABLE (SOLO_EEPROM_ACCESS_EN | SOLO_EEPROM_CS)
+
+#define SOLO_PCI_ERR 0x0070
+#define SOLO_PCI_ERR_FATAL 0x00000001
+#define SOLO_PCI_ERR_PARITY 0x00000002
+#define SOLO_PCI_ERR_TARGET 0x00000004
+#define SOLO_PCI_ERR_TIMEOUT 0x00000008
+#define SOLO_PCI_ERR_P2M 0x00000010
+#define SOLO_PCI_ERR_ATA 0x00000020
+#define SOLO_PCI_ERR_P2M_DESC 0x00000040
+#define SOLO_PCI_ERR_FSM0(__s) (((__s) >> 16) & 0x0f)
+#define SOLO_PCI_ERR_FSM1(__s) (((__s) >> 20) & 0x0f)
+#define SOLO_PCI_ERR_FSM2(__s) (((__s) >> 24) & 0x1f)
+
+#define SOLO_P2M_BASE 0x0080
+
+#define SOLO_P2M_CONFIG(n) (0x0080 + ((n)*0x20))
+#define SOLO_P2M_DMA_INTERVAL(n) ((n)<<6)/* N*32 clocks */
+#define SOLO_P2M_CSC_BYTE_REORDER BIT(5) /* BGR -> RGB */
+/* 0:r=[14:10] g=[9:5] b=[4:0], 1:r=[15:11] g=[10:5] b=[4:0] */
+#define SOLO_P2M_CSC_16BIT_565 BIT(4)
+#define SOLO_P2M_UV_SWAP BIT(3)
+#define SOLO_P2M_PCI_MASTER_MODE BIT(2)
+#define SOLO_P2M_DESC_INTR_OPT BIT(1) /* 1:Empty, 0:Each */
+#define SOLO_P2M_DESC_MODE BIT(0)
+
+#define SOLO_P2M_DES_ADR(n) (0x0084 + ((n)*0x20))
+
+#define SOLO_P2M_DESC_ID(n) (0x0088 + ((n)*0x20))
+#define SOLO_P2M_UPDATE_ID(n) ((n)<<0)
+
+#define SOLO_P2M_STATUS(n) (0x008C + ((n)*0x20))
+#define SOLO_P2M_COMMAND_DONE BIT(8)
+#define SOLO_P2M_CURRENT_ID(stat) (0xff & (stat))
+
+#define SOLO_P2M_CONTROL(n) (0x0090 + ((n)*0x20))
+#define SOLO_P2M_PCI_INC(n) ((n)<<20)
+#define SOLO_P2M_REPEAT(n) ((n)<<10)
+/* 0:512, 1:256, 2:128, 3:64, 4:32, 5:128(2page) */
+#define SOLO_P2M_BURST_SIZE(n) ((n)<<7)
+#define SOLO_P2M_BURST_512 0
+#define SOLO_P2M_BURST_256 1
+#define SOLO_P2M_BURST_128 2
+#define SOLO_P2M_BURST_64 3
+#define SOLO_P2M_BURST_32 4
+#define SOLO_P2M_CSC_16BIT BIT(6) /* 0:24bit, 1:16bit */
+/* 0:Y[0]<-0(OFF), 1:Y[0]<-1(ON), 2:Y[0]<-G[0], 3:Y[0]<-Bit[15] */
+#define SOLO_P2M_ALPHA_MODE(n) ((n)<<4)
+#define SOLO_P2M_CSC_ON BIT(3)
+#define SOLO_P2M_INTERRUPT_REQ BIT(2)
+#define SOLO_P2M_WRITE BIT(1)
+#define SOLO_P2M_TRANS_ON BIT(0)
+
+#define SOLO_P2M_EXT_CFG(n) (0x0094 + ((n)*0x20))
+#define SOLO_P2M_EXT_INC(n) ((n)<<20)
+#define SOLO_P2M_COPY_SIZE(n) ((n)<<0)
+
+#define SOLO_P2M_TAR_ADR(n) (0x0098 + ((n)*0x20))
+
+#define SOLO_P2M_EXT_ADR(n) (0x009C + ((n)*0x20))
+
+#define SOLO_P2M_BUFFER(i) (0x2000 + ((i)*4))
+
+#define SOLO_VI_CH_SWITCH_0 0x0100
+#define SOLO_VI_CH_SWITCH_1 0x0104
+#define SOLO_VI_CH_SWITCH_2 0x0108
+
+#define SOLO_VI_CH_ENA 0x010C
+#define SOLO_VI_CH_FORMAT 0x0110
+#define SOLO_VI_FD_SEL_MASK(n) ((n)<<16)
+#define SOLO_VI_PROG_MASK(n) ((n)<<0)
+
+#define SOLO_VI_FMT_CFG 0x0114
+#define SOLO_VI_FMT_CHECK_VCOUNT BIT(31)
+#define SOLO_VI_FMT_CHECK_HCOUNT BIT(30)
+#define SOLO_VI_FMT_TEST_SIGNAL BIT(28)
+
+#define SOLO_VI_PAGE_SW 0x0118
+#define SOLO_FI_INV_DISP_LIVE(n) ((n)<<8)
+#define SOLO_FI_INV_DISP_OUT(n) ((n)<<7)
+#define SOLO_DISP_SYNC_FI(n) ((n)<<6)
+#define SOLO_PIP_PAGE_ADD(n) ((n)<<3)
+#define SOLO_NORMAL_PAGE_ADD(n) ((n)<<0)
+
+#define SOLO_VI_ACT_I_P 0x011C
+#define SOLO_VI_ACT_I_S 0x0120
+#define SOLO_VI_ACT_P 0x0124
+#define SOLO_VI_FI_INVERT BIT(31)
+#define SOLO_VI_H_START(n) ((n)<<21)
+#define SOLO_VI_V_START(n) ((n)<<11)
+#define SOLO_VI_V_STOP(n) ((n)<<0)
+
+#define SOLO_VI_STATUS0 0x0128
+#define SOLO_VI_STATUS0_PAGE(__n) ((__n) & 0x07)
+#define SOLO_VI_STATUS1 0x012C
+
+/* XXX: Might be better off in kernel level disp.h */
+#define DISP_PAGE(stat) ((stat) & 0x07)
+
+#define SOLO_VI_PB_CONFIG 0x0130
+#define SOLO_VI_PB_USER_MODE BIT(1)
+#define SOLO_VI_PB_PAL BIT(0)
+#define SOLO_VI_PB_RANGE_HV 0x0134
+#define SOLO_VI_PB_HSIZE(h) ((h)<<12)
+#define SOLO_VI_PB_VSIZE(v) ((v)<<0)
+#define SOLO_VI_PB_ACT_H 0x0138
+#define SOLO_VI_PB_HSTART(n) ((n)<<12)
+#define SOLO_VI_PB_HSTOP(n) ((n)<<0)
+#define SOLO_VI_PB_ACT_V 0x013C
+#define SOLO_VI_PB_VSTART(n) ((n)<<12)
+#define SOLO_VI_PB_VSTOP(n) ((n)<<0)
+
+#define SOLO_VI_MOSAIC(ch) (0x0140 + ((ch)*4))
+#define SOLO_VI_MOSAIC_SX(x) ((x)<<24)
+#define SOLO_VI_MOSAIC_EX(x) ((x)<<16)
+#define SOLO_VI_MOSAIC_SY(x) ((x)<<8)
+#define SOLO_VI_MOSAIC_EY(x) ((x)<<0)
+
+#define SOLO_VI_WIN_CTRL0(ch) (0x0180 + ((ch)*4))
+#define SOLO_VI_WIN_CTRL1(ch) (0x01C0 + ((ch)*4))
+
+#define SOLO_VI_WIN_CHANNEL(n) ((n)<<28)
+
+#define SOLO_VI_WIN_PIP(n) ((n)<<27)
+#define SOLO_VI_WIN_SCALE(n) ((n)<<24)
+
+#define SOLO_VI_WIN_SX(x) ((x)<<12)
+#define SOLO_VI_WIN_EX(x) ((x)<<0)
+
+#define SOLO_VI_WIN_SY(x) ((x)<<12)
+#define SOLO_VI_WIN_EY(x) ((x)<<0)
+
+#define SOLO_VI_WIN_ON(ch) (0x0200 + ((ch)*4))
+
+#define SOLO_VI_WIN_SW 0x0240
+#define SOLO_VI_WIN_LIVE_AUTO_MUTE 0x0244
+
+#define SOLO_VI_MOT_ADR 0x0260
+#define SOLO_VI_MOTION_EN(mask) ((mask)<<16)
+#define SOLO_VI_MOT_CTRL 0x0264
+#define SOLO_VI_MOTION_FRAME_COUNT(n) ((n)<<24)
+#define SOLO_VI_MOTION_SAMPLE_LENGTH(n) ((n)<<16)
+#define SOLO_VI_MOTION_INTR_START_STOP BIT(15)
+#define SOLO_VI_MOTION_FREEZE_DATA BIT(14)
+#define SOLO_VI_MOTION_SAMPLE_COUNT(n) ((n)<<0)
+#define SOLO_VI_MOT_CLEAR 0x0268
+#define SOLO_VI_MOT_STATUS 0x026C
+#define SOLO_VI_MOTION_CNT(n) ((n)<<0)
+#define SOLO_VI_MOTION_BORDER 0x0270
+#define SOLO_VI_MOTION_BAR 0x0274
+#define SOLO_VI_MOTION_Y_SET BIT(29)
+#define SOLO_VI_MOTION_Y_ADD BIT(28)
+#define SOLO_VI_MOTION_CB_SET BIT(27)
+#define SOLO_VI_MOTION_CB_ADD BIT(26)
+#define SOLO_VI_MOTION_CR_SET BIT(25)
+#define SOLO_VI_MOTION_CR_ADD BIT(24)
+#define SOLO_VI_MOTION_Y_VALUE(v) ((v)<<16)
+#define SOLO_VI_MOTION_CB_VALUE(v) ((v)<<8)
+#define SOLO_VI_MOTION_CR_VALUE(v) ((v)<<0)
+
+#define SOLO_VO_FMT_ENC 0x0300
+#define SOLO_VO_SCAN_MODE_PROGRESSIVE BIT(31)
+#define SOLO_VO_FMT_TYPE_PAL BIT(30)
+#define SOLO_VO_FMT_TYPE_NTSC 0
+#define SOLO_VO_USER_SET BIT(29)
+
+#define SOLO_VO_FI_CHANGE BIT(20)
+#define SOLO_VO_USER_COLOR_SET_VSYNC BIT(19)
+#define SOLO_VO_USER_COLOR_SET_HSYNC BIT(18)
+#define SOLO_VO_USER_COLOR_SET_NAH BIT(17)
+#define SOLO_VO_USER_COLOR_SET_NAV BIT(16)
+#define SOLO_VO_NA_COLOR_Y(Y) ((Y)<<8)
+#define SOLO_VO_NA_COLOR_CB(CB) (((CB)/16)<<4)
+#define SOLO_VO_NA_COLOR_CR(CR) (((CR)/16)<<0)
+
+#define SOLO_VO_ACT_H 0x0304
+#define SOLO_VO_H_BLANK(n) ((n)<<22)
+#define SOLO_VO_H_START(n) ((n)<<11)
+#define SOLO_VO_H_STOP(n) ((n)<<0)
+
+#define SOLO_VO_ACT_V 0x0308
+#define SOLO_VO_V_BLANK(n) ((n)<<22)
+#define SOLO_VO_V_START(n) ((n)<<11)
+#define SOLO_VO_V_STOP(n) ((n)<<0)
+
+#define SOLO_VO_RANGE_HV 0x030C
+#define SOLO_VO_SYNC_INVERT BIT(24)
+#define SOLO_VO_HSYNC_INVERT BIT(23)
+#define SOLO_VO_VSYNC_INVERT BIT(22)
+#define SOLO_VO_H_LEN(n) ((n)<<11)
+#define SOLO_VO_V_LEN(n) ((n)<<0)
+
+#define SOLO_VO_DISP_CTRL 0x0310
+#define SOLO_VO_DISP_ON BIT(31)
+#define SOLO_VO_DISP_ERASE_COUNT(n) ((n&0xf)<<24)
+#define SOLO_VO_DISP_DOUBLE_SCAN BIT(22)
+#define SOLO_VO_DISP_SINGLE_PAGE BIT(21)
+#define SOLO_VO_DISP_BASE(n) (((n)>>16) & 0xffff)
+
+#define SOLO_VO_DISP_ERASE 0x0314
+#define SOLO_VO_DISP_ERASE_ON BIT(0)
+
+#define SOLO_VO_ZOOM_CTRL 0x0318
+#define SOLO_VO_ZOOM_VER_ON BIT(24)
+#define SOLO_VO_ZOOM_HOR_ON BIT(23)
+#define SOLO_VO_ZOOM_V_COMP BIT(22)
+#define SOLO_VO_ZOOM_SX(h) (((h)/2)<<11)
+#define SOLO_VO_ZOOM_SY(v) (((v)/2)<<0)
+
+#define SOLO_VO_FREEZE_CTRL 0x031C
+#define SOLO_VO_FREEZE_ON BIT(1)
+#define SOLO_VO_FREEZE_INTERPOLATION BIT(0)
+
+#define SOLO_VO_BKG_COLOR 0x0320
+#define SOLO_BG_Y(y) ((y)<<16)
+#define SOLO_BG_U(u) ((u)<<8)
+#define SOLO_BG_V(v) ((v)<<0)
+
+#define SOLO_VO_DEINTERLACE 0x0324
+#define SOLO_VO_DEINTERLACE_THRESHOLD(n) ((n)<<8)
+#define SOLO_VO_DEINTERLACE_EDGE_VALUE(n) ((n)<<0)
+
+#define SOLO_VO_BORDER_LINE_COLOR 0x0330
+#define SOLO_VO_BORDER_FILL_COLOR 0x0334
+#define SOLO_VO_BORDER_LINE_MASK 0x0338
+#define SOLO_VO_BORDER_FILL_MASK 0x033c
+
+#define SOLO_VO_BORDER_X(n) (0x0340+((n)*4))
+#define SOLO_VO_BORDER_Y(n) (0x0354+((n)*4))
+
+#define SOLO_VO_CELL_EXT_SET 0x0368
+#define SOLO_VO_CELL_EXT_START 0x036c
+#define SOLO_VO_CELL_EXT_STOP 0x0370
+
+#define SOLO_VO_CELL_EXT_SET2 0x0374
+#define SOLO_VO_CELL_EXT_START2 0x0378
+#define SOLO_VO_CELL_EXT_STOP2 0x037c
+
+#define SOLO_VO_RECTANGLE_CTRL(n) (0x0368+((n)*12))
+#define SOLO_VO_RECTANGLE_START(n) (0x036c+((n)*12))
+#define SOLO_VO_RECTANGLE_STOP(n) (0x0370+((n)*12))
+
+#define SOLO_VO_CURSOR_POS (0x0380)
+#define SOLO_VO_CURSOR_CLR (0x0384)
+#define SOLO_VO_CURSOR_CLR2 (0x0388)
+#define SOLO_VO_CURSOR_MASK(id) (0x0390+((id)*4))
+
+#define SOLO_VO_EXPANSION(id) (0x0250+((id)*4))
+
+#define SOLO_OSG_CONFIG 0x03E0
+#define SOLO_VO_OSG_ON BIT(31)
+#define SOLO_VO_OSG_COLOR_MUTE BIT(28)
+#define SOLO_VO_OSG_ALPHA_RATE(n) ((n)<<22)
+#define SOLO_VO_OSG_ALPHA_BG_RATE(n) ((n)<<16)
+#define SOLO_VO_OSG_BASE(offset) (((offset)>>16)&0xffff)
+
+#define SOLO_OSG_ERASE 0x03E4
+#define SOLO_OSG_ERASE_ON (0x80)
+#define SOLO_OSG_ERASE_OFF (0x00)
+
+#define SOLO_VO_OSG_BLINK 0x03E8
+#define SOLO_VO_OSG_BLINK_ON BIT(1)
+#define SOLO_VO_OSG_BLINK_INTREVAL18 BIT(0)
+
+#define SOLO_CAP_BASE 0x0400
+#define SOLO_CAP_MAX_PAGE(n) ((n)<<16)
+#define SOLO_CAP_BASE_ADDR(n) ((n)<<0)
+#define SOLO_CAP_BTW 0x0404
+#define SOLO_CAP_PROG_BANDWIDTH(n) ((n)<<8)
+#define SOLO_CAP_MAX_BANDWIDTH(n) ((n)<<0)
+
+#define SOLO_DIM_SCALE1 0x0408
+#define SOLO_DIM_SCALE2 0x040C
+#define SOLO_DIM_SCALE3 0x0410
+#define SOLO_DIM_SCALE4 0x0414
+#define SOLO_DIM_SCALE5 0x0418
+#define SOLO_DIM_V_MB_NUM_FRAME(n) ((n)<<16)
+#define SOLO_DIM_V_MB_NUM_FIELD(n) ((n)<<8)
+#define SOLO_DIM_H_MB_NUM(n) ((n)<<0)
+
+#define SOLO_DIM_PROG 0x041C
+#define SOLO_CAP_STATUS 0x0420
+
+#define SOLO_CAP_CH_SCALE(ch) (0x0440+((ch)*4))
+#define SOLO_CAP_CH_COMP_ENA_E(ch) (0x0480+((ch)*4))
+#define SOLO_CAP_CH_INTV(ch) (0x04C0+((ch)*4))
+#define SOLO_CAP_CH_INTV_E(ch) (0x0500+((ch)*4))
+
+
+#define SOLO_VE_CFG0 0x0610
+#define SOLO_VE_TWO_PAGE_MODE BIT(31)
+#define SOLO_VE_INTR_CTRL(n) ((n)<<24)
+#define SOLO_VE_BLOCK_SIZE(n) ((n)<<16)
+#define SOLO_VE_BLOCK_BASE(n) ((n)<<0)
+
+#define SOLO_VE_CFG1 0x0614
+#define SOLO_VE_BYTE_ALIGN(n) ((n)<<24)
+#define SOLO_VE_INSERT_INDEX BIT(18)
+#define SOLO_VE_MOTION_MODE(n) ((n)<<16)
+#define SOLO_VE_MOTION_BASE(n) ((n)<<0)
+#define SOLO_VE_MPEG_SIZE_H(n) ((n)<<28) /* 6110 Only */
+#define SOLO_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 Only */
+#define SOLO_VE_INSERT_INDEX_JPEG BIT(19) /* 6110 Only */
+
+#define SOLO_VE_WMRK_POLY 0x061C
+#define SOLO_VE_VMRK_INIT_KEY 0x0620
+#define SOLO_VE_WMRK_STRL 0x0624
+#define SOLO_VE_ENCRYP_POLY 0x0628
+#define SOLO_VE_ENCRYP_INIT 0x062C
+#define SOLO_VE_ATTR 0x0630
+#define SOLO_VE_LITTLE_ENDIAN BIT(31)
+#define SOLO_COMP_ATTR_RN BIT(30)
+#define SOLO_COMP_ATTR_FCODE(n) ((n)<<27)
+#define SOLO_COMP_TIME_INC(n) ((n)<<25)
+#define SOLO_COMP_TIME_WIDTH(n) ((n)<<21)
+#define SOLO_DCT_INTERVAL(n) ((n)<<16)
+#define SOLO_VE_COMPT_MOT 0x0634 /* 6110 Only */
+
+#define SOLO_VE_STATE(n) (0x0640+((n)*4))
+
+#define SOLO_VE_JPEG_QP_TBL 0x0670
+#define SOLO_VE_JPEG_QP_CH_L 0x0674
+#define SOLO_VE_JPEG_QP_CH_H 0x0678
+#define SOLO_VE_JPEG_CFG 0x067C
+#define SOLO_VE_JPEG_CTRL 0x0680
+#define SOLO_VE_CODE_ENCRYPT 0x0684 /* 6110 Only */
+#define SOLO_VE_JPEG_CFG1 0x0688 /* 6110 Only */
+#define SOLO_VE_WMRK_ENABLE 0x068C /* 6110 Only */
+#define SOLO_VE_OSD_CH 0x0690
+#define SOLO_VE_OSD_BASE 0x0694
+#define SOLO_VE_OSD_CLR 0x0698
+#define SOLO_VE_OSD_OPT 0x069C
+#define SOLO_VE_OSD_V_DOUBLE BIT(16) /* 6110 Only */
+#define SOLO_VE_OSD_H_SHADOW BIT(15)
+#define SOLO_VE_OSD_V_SHADOW BIT(14)
+#define SOLO_VE_OSD_H_OFFSET(n) ((n & 0x7f)<<7)
+#define SOLO_VE_OSD_V_OFFSET(n) (n & 0x7f)
+
+#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4))
+#define SOLO_VE_CH_MOT(ch) (0x0740+((ch)*4))
+#define SOLO_VE_CH_QP(ch) (0x0780+((ch)*4))
+#define SOLO_VE_CH_QP_E(ch) (0x07C0+((ch)*4))
+#define SOLO_VE_CH_GOP(ch) (0x0800+((ch)*4))
+#define SOLO_VE_CH_GOP_E(ch) (0x0840+((ch)*4))
+#define SOLO_VE_CH_REF_BASE(ch) (0x0880+((ch)*4))
+#define SOLO_VE_CH_REF_BASE_E(ch) (0x08C0+((ch)*4))
+
+#define SOLO_VE_MPEG4_QUE(n) (0x0A00+((n)*8))
+#define SOLO_VE_JPEG_QUE(n) (0x0A04+((n)*8))
+
+#define SOLO_VD_CFG0 0x0900
+#define SOLO_VD_CFG_NO_WRITE_NO_WINDOW BIT(24)
+#define SOLO_VD_CFG_BUSY_WIAT_CODE BIT(23)
+#define SOLO_VD_CFG_BUSY_WIAT_REF BIT(22)
+#define SOLO_VD_CFG_BUSY_WIAT_RES BIT(21)
+#define SOLO_VD_CFG_BUSY_WIAT_MS BIT(20)
+#define SOLO_VD_CFG_SINGLE_MODE BIT(18)
+#define SOLO_VD_CFG_SCAL_MANUAL BIT(17)
+#define SOLO_VD_CFG_USER_PAGE_CTRL BIT(16)
+#define SOLO_VD_CFG_LITTLE_ENDIAN BIT(15)
+#define SOLO_VD_CFG_START_FI BIT(14)
+#define SOLO_VD_CFG_ERR_LOCK BIT(13)
+#define SOLO_VD_CFG_ERR_INT_ENA BIT(12)
+#define SOLO_VD_CFG_TIME_WIDTH(n) ((n)<<8)
+#define SOLO_VD_CFG_DCT_INTERVAL(n) ((n)<<0)
+
+#define SOLO_VD_CFG1 0x0904
+
+#define SOLO_VD_DEINTERLACE 0x0908
+#define SOLO_VD_DEINTERLACE_THRESHOLD(n) ((n)<<8)
+#define SOLO_VD_DEINTERLACE_EDGE_VALUE(n) ((n)<<0)
+
+#define SOLO_VD_CODE_ADR 0x090C
+
+#define SOLO_VD_CTRL 0x0910
+#define SOLO_VD_OPER_ON BIT(31)
+#define SOLO_VD_MAX_ITEM(n) ((n)<<0)
+
+#define SOLO_VD_STATUS0 0x0920
+#define SOLO_VD_STATUS0_INTR_ACK BIT(22)
+#define SOLO_VD_STATUS0_INTR_EMPTY BIT(21)
+#define SOLO_VD_STATUS0_INTR_ERR BIT(20)
+
+#define SOLO_VD_STATUS1 0x0924
+
+#define SOLO_VD_IDX0 0x0930
+#define SOLO_VD_IDX_INTERLACE BIT(30)
+#define SOLO_VD_IDX_CHANNEL(n) ((n)<<24)
+#define SOLO_VD_IDX_SIZE(n) ((n)<<0)
+
+#define SOLO_VD_IDX1 0x0934
+#define SOLO_VD_IDX_SRC_SCALE(n) ((n)<<28)
+#define SOLO_VD_IDX_WINDOW(n) ((n)<<24)
+#define SOLO_VD_IDX_DEINTERLACE BIT(16)
+#define SOLO_VD_IDX_H_BLOCK(n) ((n)<<8)
+#define SOLO_VD_IDX_V_BLOCK(n) ((n)<<0)
+
+#define SOLO_VD_IDX2 0x0938
+#define SOLO_VD_IDX_REF_BASE_SIDE BIT(31)
+#define SOLO_VD_IDX_REF_BASE(n) (((n)>>16)&0xffff)
+
+#define SOLO_VD_IDX3 0x093C
+#define SOLO_VD_IDX_DISP_SCALE(n) ((n)<<28)
+#define SOLO_VD_IDX_INTERLACE_WR BIT(27)
+#define SOLO_VD_IDX_INTERPOL BIT(26)
+#define SOLO_VD_IDX_HOR2X BIT(25)
+#define SOLO_VD_IDX_OFFSET_X(n) ((n)<<12)
+#define SOLO_VD_IDX_OFFSET_Y(n) ((n)<<0)
+
+#define SOLO_VD_IDX4 0x0940
+#define SOLO_VD_IDX_DEC_WR_PAGE(n) ((n)<<8)
+#define SOLO_VD_IDX_DISP_RD_PAGE(n) ((n)<<0)
+
+#define SOLO_VD_WR_PAGE(n) (0x03F0 + ((n) * 4))
+
+
+#define SOLO_GPIO_CONFIG_0 0x0B00
+#define SOLO_GPIO_CONFIG_1 0x0B04
+#define SOLO_GPIO_DATA_OUT 0x0B08
+#define SOLO_GPIO_DATA_IN 0x0B0C
+#define SOLO_GPIO_INT_ACK_STA 0x0B10
+#define SOLO_GPIO_INT_ENA 0x0B14
+#define SOLO_GPIO_INT_CFG_0 0x0B18
+#define SOLO_GPIO_INT_CFG_1 0x0B1C
+
+
+#define SOLO_IIC_CFG 0x0B20
+#define SOLO_IIC_ENABLE BIT(8)
+#define SOLO_IIC_PRESCALE(n) ((n)<<0)
+
+#define SOLO_IIC_CTRL 0x0B24
+#define SOLO_IIC_AUTO_CLEAR BIT(20)
+#define SOLO_IIC_STATE_RX_ACK BIT(19)
+#define SOLO_IIC_STATE_BUSY BIT(18)
+#define SOLO_IIC_STATE_SIG_ERR BIT(17)
+#define SOLO_IIC_STATE_TRNS BIT(16)
+#define SOLO_IIC_CH_SET(n) ((n)<<5)
+#define SOLO_IIC_ACK_EN BIT(4)
+#define SOLO_IIC_START BIT(3)
+#define SOLO_IIC_STOP BIT(2)
+#define SOLO_IIC_READ BIT(1)
+#define SOLO_IIC_WRITE BIT(0)
+
+#define SOLO_IIC_TXD 0x0B28
+#define SOLO_IIC_RXD 0x0B2C
+
+/*
+ * UART REGISTER
+ */
+#define SOLO_UART_CONTROL(n) (0x0BA0 + ((n)*0x20))
+#define SOLO_UART_CLK_DIV(n) ((n)<<24)
+#define SOLO_MODEM_CTRL_EN BIT(20)
+#define SOLO_PARITY_ERROR_DROP BIT(18)
+#define SOLO_IRQ_ERR_EN BIT(17)
+#define SOLO_IRQ_RX_EN BIT(16)
+#define SOLO_IRQ_TX_EN BIT(15)
+#define SOLO_RX_EN BIT(14)
+#define SOLO_TX_EN BIT(13)
+#define SOLO_UART_HALF_DUPLEX BIT(12)
+#define SOLO_UART_LOOPBACK BIT(11)
+
+#define SOLO_BAUDRATE_230400 ((0<<9)|(0<<6))
+#define SOLO_BAUDRATE_115200 ((0<<9)|(1<<6))
+#define SOLO_BAUDRATE_57600 ((0<<9)|(2<<6))
+#define SOLO_BAUDRATE_38400 ((0<<9)|(3<<6))
+#define SOLO_BAUDRATE_19200 ((0<<9)|(4<<6))
+#define SOLO_BAUDRATE_9600 ((0<<9)|(5<<6))
+#define SOLO_BAUDRATE_4800 ((0<<9)|(6<<6))
+#define SOLO_BAUDRATE_2400 ((1<<9)|(6<<6))
+#define SOLO_BAUDRATE_1200 ((2<<9)|(6<<6))
+#define SOLO_BAUDRATE_300 ((3<<9)|(6<<6))
+
+#define SOLO_UART_DATA_BIT_8 (3<<4)
+#define SOLO_UART_DATA_BIT_7 (2<<4)
+#define SOLO_UART_DATA_BIT_6 (1<<4)
+#define SOLO_UART_DATA_BIT_5 (0<<4)
+
+#define SOLO_UART_STOP_BIT_1 (0<<2)
+#define SOLO_UART_STOP_BIT_2 (1<<2)
+
+#define SOLO_UART_PARITY_NONE (0<<0)
+#define SOLO_UART_PARITY_EVEN (2<<0)
+#define SOLO_UART_PARITY_ODD (3<<0)
+
+#define SOLO_UART_STATUS(n) (0x0BA4 + ((n)*0x20))
+#define SOLO_UART_CTS BIT(15)
+#define SOLO_UART_RX_BUSY BIT(14)
+#define SOLO_UART_OVERRUN BIT(13)
+#define SOLO_UART_FRAME_ERR BIT(12)
+#define SOLO_UART_PARITY_ERR BIT(11)
+#define SOLO_UART_TX_BUSY BIT(5)
+
+#define SOLO_UART_RX_BUFF_CNT(stat) (((stat)>>6) & 0x1f)
+#define SOLO_UART_RX_BUFF_SIZE 8
+#define SOLO_UART_TX_BUFF_CNT(stat) (((stat)>>0) & 0x1f)
+#define SOLO_UART_TX_BUFF_SIZE 8
+
+#define SOLO_UART_TX_DATA(n) (0x0BA8 + ((n)*0x20))
+#define SOLO_UART_TX_DATA_PUSH BIT(8)
+#define SOLO_UART_RX_DATA(n) (0x0BAC + ((n)*0x20))
+#define SOLO_UART_RX_DATA_POP BIT(8)
+
+#define SOLO_TIMER_CLOCK_NUM 0x0be0
+#define SOLO_TIMER_USEC 0x0be8
+#define SOLO_TIMER_SEC 0x0bec
+#define SOLO_TIMER_USEC_LSB 0x0d20 /* 6110 Only */
+
+#define SOLO_AUDIO_CONTROL 0x0D00
+#define SOLO_AUDIO_ENABLE BIT(31)
+#define SOLO_AUDIO_MASTER_MODE BIT(30)
+#define SOLO_AUDIO_I2S_MODE BIT(29)
+#define SOLO_AUDIO_I2S_LR_SWAP BIT(27)
+#define SOLO_AUDIO_I2S_8BIT BIT(26)
+#define SOLO_AUDIO_I2S_MULTI(n) ((n)<<24)
+#define SOLO_AUDIO_MIX_9TO0 BIT(23)
+#define SOLO_AUDIO_DEC_9TO0_VOL(n) ((n)<<20)
+#define SOLO_AUDIO_MIX_19TO10 BIT(19)
+#define SOLO_AUDIO_DEC_19TO10_VOL(n) ((n)<<16)
+#define SOLO_AUDIO_MODE(n) ((n)<<0)
+#define SOLO_AUDIO_SAMPLE 0x0D04
+#define SOLO_AUDIO_EE_MODE_ON BIT(30)
+#define SOLO_AUDIO_EE_ENC_CH(ch) ((ch)<<25)
+#define SOLO_AUDIO_BITRATE(n) ((n)<<16)
+#define SOLO_AUDIO_CLK_DIV(n) ((n)<<0)
+#define SOLO_AUDIO_FDMA_INTR 0x0D08
+#define SOLO_AUDIO_FDMA_INTERVAL(n) ((n)<<19)
+#define SOLO_AUDIO_INTR_ORDER(n) ((n)<<16)
+#define SOLO_AUDIO_FDMA_BASE(n) ((n)<<0)
+#define SOLO_AUDIO_EVOL_0 0x0D0C
+#define SOLO_AUDIO_EVOL_1 0x0D10
+#define SOLO_AUDIO_EVOL(ch, value) ((value)<<((ch)%10))
+#define SOLO_AUDIO_STA 0x0D14
+
+/*
+ * Watchdog configuration
+ */
+#define SOLO_WATCHDOG 0x0be4
+#define SOLO_WATCHDOG_SET(status, sec) (status << 8 | (sec & 0xff))
+
+#endif /* __SOLO6X10_REGISTERS_H */
diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.c b/drivers/media/pci/solo6x10/solo6x10-tw28.c
new file mode 100644
index 000000000..1b7c22a9b
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-tw28.c
@@ -0,0 +1,863 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+#define DEFAULT_HDELAY_NTSC (32 - 8)
+#define DEFAULT_HACTIVE_NTSC (720 + 16)
+#define DEFAULT_VDELAY_NTSC (7 - 2)
+#define DEFAULT_VACTIVE_NTSC (240 + 4)
+
+#define DEFAULT_HDELAY_PAL (32 + 4)
+#define DEFAULT_HACTIVE_PAL (864-DEFAULT_HDELAY_PAL)
+#define DEFAULT_VDELAY_PAL (6)
+#define DEFAULT_VACTIVE_PAL (312-DEFAULT_VDELAY_PAL)
+
+
+static const u8 tbl_tw2864_ntsc_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x30 */
+ 0x12, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00,
+ 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xa0, 0x88, 0x5a, 0x01,
+ 0x08, 0x08, 0x08, 0x08, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44,
+ 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00,
+ 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81,
+ 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xb5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00,
+};
+
+static const u8 tbl_tw2864_pal_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */
+ 0x18, 0xf5, 0x0c, 0xd0, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x00,
+ 0x00, 0x02, 0x00, 0xcc, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xd8, 0xbc, 0xb8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x72, 0x3e, 0x14, 0xa5, 0xe4, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xa0, 0x90, 0x5a, 0x01,
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x1a, 0x1a, 0x1a, 0x1a, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, 0xf0, 0x44,
+ 0x44, 0x0a, 0x00, 0xff, 0xef, 0xef, 0xef, 0xef, /* 0xb0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xb1, 0xe4, 0x40, 0x00,
+ 0x77, 0x77, 0x01, 0x13, 0x57, 0x9b, 0xdf, 0x20, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xc1, 0x0f, 0x11, 0x11, 0x81,
+ 0x00, 0xe0, 0xbb, 0xbb, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xb5, 0x09, 0x00, 0xa0, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x11, 0x40, 0xaf, 0xff, 0x00, 0x00, 0x00,
+};
+
+static const u8 tbl_tw2865_ntsc_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x00 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x10 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x02, /* 0x20 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0xf0, 0x70, 0x48, 0x80, 0x80, 0x00, 0x02, /* 0x30 */
+ 0x12, 0xff, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+ 0x00, 0x00, 0x90, 0x68, 0x00, 0x38, 0x80, 0x80, /* 0x40 */
+ 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43,
+ 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */
+ 0xE9, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80,
+ 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13,
+ 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1B, 0x1A, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44,
+ 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */
+ 0xFF, 0xE7, 0xE9, 0xE9, 0xEB, 0xFF, 0xD6, 0xD8,
+ 0xD8, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80,
+ 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81,
+ 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xB5, 0x09, 0x78, 0x85, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0,
+};
+
+static const u8 tbl_tw2865_pal_template[] = {
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x00 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x10 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x20 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0xf0, 0x70, 0x30, 0x80, 0x80, 0x00, 0x12, /* 0x30 */
+ 0x11, 0xff, 0x01, 0xc3, 0x00, 0x00, 0x01, 0x7f,
+ 0x00, 0x94, 0x90, 0x48, 0x00, 0x38, 0x7F, 0x80, /* 0x40 */
+ 0x80, 0x80, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x45, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43,
+ 0x08, 0x00, 0x00, 0x01, 0xf1, 0x03, 0xEF, 0x03, /* 0x70 */
+ 0xEA, 0x03, 0xD9, 0x15, 0x15, 0xE4, 0xA3, 0x80,
+ 0x00, 0x02, 0x00, 0xCC, 0x00, 0x80, 0x44, 0x50, /* 0x80 */
+ 0x22, 0x01, 0xD8, 0xBC, 0xB8, 0x44, 0x38, 0x00,
+ 0x00, 0x78, 0x44, 0x3D, 0x14, 0xA5, 0xE0, 0x05, /* 0x90 */
+ 0x00, 0x28, 0x44, 0x44, 0xA0, 0x90, 0x52, 0x13,
+ 0x08, 0x08, 0x08, 0x08, 0x1A, 0x1A, 0x1A, 0x1A, /* 0xa0 */
+ 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x44,
+ 0x44, 0x4A, 0x00, 0xFF, 0xEF, 0xEF, 0xEF, 0xEF, /* 0xb0 */
+ 0xFF, 0xE7, 0xE9, 0xE9, 0xE9, 0xFF, 0xD7, 0xD8,
+ 0xD9, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */
+ 0x00, 0x00, 0x55, 0x00, 0xE4, 0x39, 0x00, 0x80,
+ 0x77, 0x77, 0x03, 0x20, 0x57, 0x9b, 0xdf, 0x31, /* 0xd0 */
+ 0x64, 0xa8, 0xec, 0xd1, 0x0f, 0x11, 0x11, 0x81,
+ 0x10, 0xC0, 0xAA, 0xAA, 0x00, 0x11, 0x00, 0x00, /* 0xe0 */
+ 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0x11, 0x00,
+ 0x83, 0xB5, 0x09, 0x00, 0xA0, 0x00, 0x01, 0x20, /* 0xf0 */
+ 0x64, 0x51, 0x40, 0xaf, 0xFF, 0xF0, 0x00, 0xC0,
+};
+
+#define is_tw286x(__solo, __id) (!(__solo->tw2815 & (1 << __id)))
+
+static u8 tw_readbyte(struct solo_dev *solo_dev, int chip_id, u8 tw6x_off,
+ u8 tw_off)
+{
+ if (is_tw286x(solo_dev, chip_id))
+ return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw6x_off);
+ else
+ return solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw_off);
+}
+
+static void tw_writebyte(struct solo_dev *solo_dev, int chip_id,
+ u8 tw6x_off, u8 tw_off, u8 val)
+{
+ if (is_tw286x(solo_dev, chip_id))
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw6x_off, val);
+ else
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_id),
+ tw_off, val);
+}
+
+static void tw_write_and_verify(struct solo_dev *solo_dev, u8 addr, u8 off,
+ u8 val)
+{
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ u8 rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW, addr, off);
+
+ if (rval == val)
+ return;
+
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, addr, off, val);
+ msleep_interruptible(1);
+ }
+
+/* printk("solo6x10/tw28: Error writing register: %02x->%02x [%02x]\n", */
+/* addr, off, val); */
+}
+
+static int tw2865_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_tw2865_common[256];
+ int i;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ memcpy(tbl_tw2865_common, tbl_tw2865_pal_template,
+ sizeof(tbl_tw2865_common));
+ else
+ memcpy(tbl_tw2865_common, tbl_tw2865_ntsc_template,
+ sizeof(tbl_tw2865_common));
+
+ /* ALINK Mode */
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2865_common[0xd2] = 0x01;
+ tbl_tw2865_common[0xcf] = 0x00;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2865_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2865_common[0xcf] = 0x80;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2865_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2865_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2865_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2865_common[0xcf] = 0x80;
+ }
+
+ for (i = 0; i < 0xff; i++) {
+ /* Skip read only registers */
+ switch (i) {
+ case 0xb8 ... 0xc1:
+ case 0xc4 ... 0xc7:
+ case 0xfd:
+ continue;
+ }
+ switch (i & ~0x30) {
+ case 0x00:
+ case 0x0c ... 0x0d:
+ continue;
+ }
+
+ tw_write_and_verify(solo_dev, dev_addr, i,
+ tbl_tw2865_common[i]);
+ }
+
+ return 0;
+}
+
+static int tw2864_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_tw2864_common[256];
+ int i;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ memcpy(tbl_tw2864_common, tbl_tw2864_pal_template,
+ sizeof(tbl_tw2864_common));
+ else
+ memcpy(tbl_tw2864_common, tbl_tw2864_ntsc_template,
+ sizeof(tbl_tw2864_common));
+
+ if (solo_dev->tw2865 == 0) {
+ /* IRQ Mode */
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2864_common[0xd2] = 0x01;
+ tbl_tw2864_common[0xcf] = 0x00;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2864_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x40;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2864_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2864_common[0xcf] = 0x43;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2864_common[0xcf] = 0x40;
+ }
+ } else {
+ /* ALINK Mode. Assumes that the first tw28xx is a
+ * 2865 and these are in cascade. */
+ for (i = 0; i <= 4; i++)
+ tbl_tw2864_common[0x08 | i << 4] = 0x12;
+
+ if (solo_dev->nr_chans == 8) {
+ tbl_tw2864_common[0xd2] = 0x02;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x80;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2864_common[0xd2] = 0x03;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2864_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2864_common[0xcf] = 0x83;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2864_common[0xcf] = 0x80;
+ }
+ }
+
+ for (i = 0; i < 0xff; i++) {
+ /* Skip read only registers */
+ switch (i) {
+ case 0xb8 ... 0xc1:
+ case 0xfd:
+ continue;
+ }
+ switch (i & ~0x30) {
+ case 0x00:
+ case 0x0c:
+ case 0x0d:
+ continue;
+ }
+
+ tw_write_and_verify(solo_dev, dev_addr, i,
+ tbl_tw2864_common[i]);
+ }
+
+ return 0;
+}
+
+static int tw2815_setup(struct solo_dev *solo_dev, u8 dev_addr)
+{
+ u8 tbl_ntsc_tw2815_common[] = {
+ 0x00, 0xc8, 0x20, 0xd0, 0x06, 0xf0, 0x08, 0x80,
+ 0x80, 0x80, 0x80, 0x02, 0x06, 0x00, 0x11,
+ };
+
+ u8 tbl_pal_tw2815_common[] = {
+ 0x00, 0x88, 0x20, 0xd0, 0x05, 0x20, 0x28, 0x80,
+ 0x80, 0x80, 0x80, 0x82, 0x06, 0x00, 0x11,
+ };
+
+ u8 tbl_tw2815_sfr[] = {
+ 0x00, 0x00, 0x00, 0xc0, 0x45, 0xa0, 0xd0, 0x2f, /* 0x00 */
+ 0x64, 0x80, 0x80, 0x82, 0x82, 0x00, 0x00, 0x00,
+ 0x00, 0x0f, 0x05, 0x00, 0x00, 0x80, 0x06, 0x00, /* 0x10 */
+ 0x00, 0x00, 0x00, 0xff, 0x8f, 0x00, 0x00, 0x00,
+ 0x88, 0x88, 0xc0, 0x00, 0x20, 0x64, 0xa8, 0xec, /* 0x20 */
+ 0x31, 0x75, 0xb9, 0xfd, 0x00, 0x00, 0x88, 0x88,
+ 0x88, 0x11, 0x00, 0x88, 0x88, 0x00, /* 0x30 */
+ };
+ u8 *tbl_tw2815_common;
+ int i;
+ int ch;
+
+ tbl_ntsc_tw2815_common[0x06] = 0;
+
+ /* Horizontal Delay Control */
+ tbl_ntsc_tw2815_common[0x02] = DEFAULT_HDELAY_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_NTSC >> 8);
+
+ /* Horizontal Active Control */
+ tbl_ntsc_tw2815_common[0x03] = DEFAULT_HACTIVE_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x03 & (DEFAULT_HACTIVE_NTSC >> 8)) << 2);
+
+ /* Vertical Delay Control */
+ tbl_ntsc_tw2815_common[0x04] = DEFAULT_VDELAY_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VDELAY_NTSC >> 8)) << 4);
+
+ /* Vertical Active Control */
+ tbl_ntsc_tw2815_common[0x05] = DEFAULT_VACTIVE_NTSC & 0xff;
+ tbl_ntsc_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VACTIVE_NTSC >> 8)) << 5);
+
+ tbl_pal_tw2815_common[0x06] = 0;
+
+ /* Horizontal Delay Control */
+ tbl_pal_tw2815_common[0x02] = DEFAULT_HDELAY_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |= 0x03 & (DEFAULT_HDELAY_PAL >> 8);
+
+ /* Horizontal Active Control */
+ tbl_pal_tw2815_common[0x03] = DEFAULT_HACTIVE_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x03 & (DEFAULT_HACTIVE_PAL >> 8)) << 2);
+
+ /* Vertical Delay Control */
+ tbl_pal_tw2815_common[0x04] = DEFAULT_VDELAY_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VDELAY_PAL >> 8)) << 4);
+
+ /* Vertical Active Control */
+ tbl_pal_tw2815_common[0x05] = DEFAULT_VACTIVE_PAL & 0xff;
+ tbl_pal_tw2815_common[0x06] |=
+ ((0x01 & (DEFAULT_VACTIVE_PAL >> 8)) << 5);
+
+ tbl_tw2815_common =
+ (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) ?
+ tbl_ntsc_tw2815_common : tbl_pal_tw2815_common;
+
+ /* Dual ITU-R BT.656 format */
+ tbl_tw2815_common[0x0d] |= 0x04;
+
+ /* Audio configuration */
+ tbl_tw2815_sfr[0x62 - 0x40] &= ~(3 << 6);
+
+ if (solo_dev->nr_chans == 4) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 1;
+ tbl_tw2815_sfr[0x62 - 0x40] |= 3 << 6;
+ } else if (solo_dev->nr_chans == 8) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 2;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6;
+ } else if (solo_dev->nr_chans == 16) {
+ tbl_tw2815_sfr[0x63 - 0x40] |= 3;
+ if (dev_addr == TW_CHIP_OFFSET_ADDR(0))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 1 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(1))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(2))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 6;
+ else if (dev_addr == TW_CHIP_OFFSET_ADDR(3))
+ tbl_tw2815_sfr[0x62 - 0x40] |= 2 << 6;
+ }
+
+ /* Output mode of R_ADATM pin (0 mixing, 1 record) */
+ /* tbl_tw2815_sfr[0x63 - 0x40] |= 0 << 2; */
+
+ /* 8KHz, used to be 16KHz, but changed for remote client compat */
+ tbl_tw2815_sfr[0x62 - 0x40] |= 0 << 2;
+ tbl_tw2815_sfr[0x6c - 0x40] |= 0 << 2;
+
+ /* Playback of right channel */
+ tbl_tw2815_sfr[0x6c - 0x40] |= 1 << 5;
+
+ /* Reserved value (XXX ??) */
+ tbl_tw2815_sfr[0x5c - 0x40] |= 1 << 5;
+
+ /* Analog output gain and mix ratio playback on full */
+ tbl_tw2815_sfr[0x70 - 0x40] |= 0xff;
+ /* Select playback audio and mute all except */
+ tbl_tw2815_sfr[0x71 - 0x40] |= 0x10;
+ tbl_tw2815_sfr[0x6d - 0x40] |= 0x0f;
+
+ /* End of audio configuration */
+
+ for (ch = 0; ch < 4; ch++) {
+ tbl_tw2815_common[0x0d] &= ~3;
+ switch (ch) {
+ case 0:
+ tbl_tw2815_common[0x0d] |= 0x21;
+ break;
+ case 1:
+ tbl_tw2815_common[0x0d] |= 0x20;
+ break;
+ case 2:
+ tbl_tw2815_common[0x0d] |= 0x23;
+ break;
+ case 3:
+ tbl_tw2815_common[0x0d] |= 0x22;
+ break;
+ }
+
+ for (i = 0; i < 0x0f; i++) {
+ if (i == 0x00)
+ continue; /* read-only */
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ dev_addr, (ch * 0x10) + i,
+ tbl_tw2815_common[i]);
+ }
+ }
+
+ for (i = 0x40; i < 0x76; i++) {
+ /* Skip read-only and nop registers */
+ if (i == 0x40 || i == 0x59 || i == 0x5a ||
+ i == 0x5d || i == 0x5e || i == 0x5f)
+ continue;
+
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW, dev_addr, i,
+ tbl_tw2815_sfr[i - 0x40]);
+ }
+
+ return 0;
+}
+
+#define FIRST_ACTIVE_LINE 0x0008
+#define LAST_ACTIVE_LINE 0x0102
+
+static void saa712x_write_regs(struct solo_dev *dev, const u8 *vals,
+ int start, int n)
+{
+ for (; start < n; start++, vals++) {
+ /* Skip read-only registers */
+ switch (start) {
+ /* case 0x00 ... 0x25: */
+ case 0x2e ... 0x37:
+ case 0x60:
+ case 0x7d:
+ continue;
+ }
+ solo_i2c_writebyte(dev, SOLO_I2C_SAA, 0x46, start, *vals);
+ }
+}
+
+#define SAA712x_reg7c (0x80 | ((LAST_ACTIVE_LINE & 0x100) >> 2) \
+ | ((FIRST_ACTIVE_LINE & 0x100) >> 4))
+
+static void saa712x_setup(struct solo_dev *dev)
+{
+ const int reg_start = 0x26;
+ static const u8 saa7128_regs_ntsc[] = {
+ /* :0x26 */
+ 0x0d, 0x00,
+ /* :0x28 */
+ 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f,
+ /* :0x2e XXX: read-only */
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x38 */
+ 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x40 */
+ 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18,
+ 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f,
+ /* :0x50 */
+ 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06,
+ 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e,
+ /* :0x60 */
+ 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77,
+ 0x41, 0x88, 0x41, 0x52, 0xed, 0x10, 0x10, 0x00,
+ /* :0x70 */
+ 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, FIRST_ACTIVE_LINE, LAST_ACTIVE_LINE & 0xff,
+ SAA712x_reg7c, 0x00, 0xff, 0xff,
+ }, saa7128_regs_pal[] = {
+ /* :0x26 */
+ 0x0d, 0x00,
+ /* :0x28 */
+ 0xe1, 0x1d, 0x75, 0x3f, 0x06, 0x3f,
+ /* :0x2e XXX: read-only */
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x38 */
+ 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* :0x40 */
+ 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18,
+ 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f,
+ /* :0x50 */
+ 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06,
+ 0x02, 0x80, 0x0f, 0x77, 0xa7, 0x67, 0x66, 0x2e,
+ /* :0x60 */
+ 0x7b, 0x02, 0x35, 0xcb, 0x8a, 0x09, 0x2a, 0x77,
+ 0x41, 0x88, 0x41, 0x52, 0xf1, 0x10, 0x20, 0x00,
+ /* :0x70 */
+ 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x30,
+ SAA712x_reg7c | 0x40, 0x00, 0xff, 0xff,
+ };
+
+ if (dev->video_type == SOLO_VO_FMT_TYPE_PAL)
+ saa712x_write_regs(dev, saa7128_regs_pal, reg_start,
+ sizeof(saa7128_regs_pal));
+ else
+ saa712x_write_regs(dev, saa7128_regs_ntsc, reg_start,
+ sizeof(saa7128_regs_ntsc));
+}
+
+int solo_tw28_init(struct solo_dev *solo_dev)
+{
+ int i;
+ u8 value;
+
+ solo_dev->tw28_cnt = 0;
+
+ /* Detect techwell chip type(s) */
+ for (i = 0; i < solo_dev->nr_chans / 4; i++) {
+ value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(i), 0xFF);
+
+ switch (value >> 3) {
+ case 0x18:
+ solo_dev->tw2865 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ break;
+ case 0x0c:
+ case 0x0d:
+ solo_dev->tw2864 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ break;
+ default:
+ value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(i),
+ 0x59);
+ if ((value >> 3) == 0x04) {
+ solo_dev->tw2815 |= 1 << i;
+ solo_dev->tw28_cnt++;
+ }
+ }
+ }
+
+ if (solo_dev->tw28_cnt != (solo_dev->nr_chans >> 2)) {
+ dev_err(&solo_dev->pdev->dev,
+ "Could not initialize any techwell chips\n");
+ return -EINVAL;
+ }
+
+ saa712x_setup(solo_dev);
+
+ for (i = 0; i < solo_dev->tw28_cnt; i++) {
+ if ((solo_dev->tw2865 & (1 << i)))
+ tw2865_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ else if ((solo_dev->tw2864 & (1 << i)))
+ tw2864_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ else
+ tw2815_setup(solo_dev, TW_CHIP_OFFSET_ADDR(i));
+ }
+
+ return 0;
+}
+
+/*
+ * We accessed the video status signal in the Techwell chip through
+ * iic/i2c because the video status reported by register REG_VI_STATUS1
+ * (address 0x012C) of the SOLO6010 chip doesn't give the correct video
+ * status signal values.
+ */
+int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch)
+{
+ u8 val, chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ val = tw_readbyte(solo_dev, chip_num, TW286x_AV_STAT_ADDR,
+ TW_AV_STAT_ADDR) & 0x0f;
+
+ return val & (1 << ch) ? 1 : 0;
+}
+
+#if 0
+/* Status of audio from up to 4 techwell chips are combined into 1 variable.
+ * See techwell datasheet for details. */
+u16 tw28_get_audio_status(struct solo_dev *solo_dev)
+{
+ u8 val;
+ u16 status = 0;
+ int i;
+
+ for (i = 0; i < solo_dev->tw28_cnt; i++) {
+ val = (tw_readbyte(solo_dev, i, TW286x_AV_STAT_ADDR,
+ TW_AV_STAT_ADDR) & 0xf0) >> 4;
+ status |= val << (i * 4);
+ }
+
+ return status;
+}
+#endif
+
+bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch)
+{
+ return is_tw286x(solo_dev, ch / 4);
+}
+
+int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
+ s32 val)
+{
+ char sval;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ if (val > 255 || val < 0)
+ return -ERANGE;
+
+ switch (ctrl) {
+ case V4L2_CID_SHARPNESS:
+ /* Only 286x has sharpness */
+ if (is_tw286x(solo_dev, chip_num)) {
+ u8 v = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num));
+ v &= 0xf0;
+ v |= val;
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num), v);
+ } else {
+ return -EINVAL;
+ }
+ break;
+
+ case V4L2_CID_HUE:
+ if (is_tw286x(solo_dev, chip_num))
+ sval = val - 128;
+ else
+ sval = (char)val;
+ tw_writebyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
+ TW_HUE_ADDR(ch), sval);
+
+ break;
+
+ case V4L2_CID_SATURATION:
+ /* 286x chips have a U and V component for saturation */
+ if (is_tw286x(solo_dev, chip_num)) {
+ solo_i2c_writebyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SATURATIONU_ADDR(ch), val);
+ }
+ tw_writebyte(solo_dev, chip_num, TW286x_SATURATIONV_ADDR(ch),
+ TW_SATURATION_ADDR(ch), val);
+
+ break;
+
+ case V4L2_CID_CONTRAST:
+ tw_writebyte(solo_dev, chip_num, TW286x_CONTRAST_ADDR(ch),
+ TW_CONTRAST_ADDR(ch), val);
+ break;
+
+ case V4L2_CID_BRIGHTNESS:
+ if (is_tw286x(solo_dev, chip_num))
+ sval = val - 128;
+ else
+ sval = (char)val;
+ tw_writebyte(solo_dev, chip_num, TW286x_BRIGHTNESS_ADDR(ch),
+ TW_BRIGHTNESS_ADDR(ch), sval);
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch,
+ s32 *val)
+{
+ u8 rval, chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ switch (ctrl) {
+ case V4L2_CID_SHARPNESS:
+ /* Only 286x has sharpness */
+ if (is_tw286x(solo_dev, chip_num)) {
+ rval = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+ TW_CHIP_OFFSET_ADDR(chip_num),
+ TW286x_SHARPNESS(chip_num));
+ *val = rval & 0x0f;
+ } else
+ *val = 0;
+ break;
+ case V4L2_CID_HUE:
+ rval = tw_readbyte(solo_dev, chip_num, TW286x_HUE_ADDR(ch),
+ TW_HUE_ADDR(ch));
+ if (is_tw286x(solo_dev, chip_num))
+ *val = (s32)((char)rval) + 128;
+ else
+ *val = rval;
+ break;
+ case V4L2_CID_SATURATION:
+ *val = tw_readbyte(solo_dev, chip_num,
+ TW286x_SATURATIONU_ADDR(ch),
+ TW_SATURATION_ADDR(ch));
+ break;
+ case V4L2_CID_CONTRAST:
+ *val = tw_readbyte(solo_dev, chip_num,
+ TW286x_CONTRAST_ADDR(ch),
+ TW_CONTRAST_ADDR(ch));
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ rval = tw_readbyte(solo_dev, chip_num,
+ TW286x_BRIGHTNESS_ADDR(ch),
+ TW_BRIGHTNESS_ADDR(ch));
+ if (is_tw286x(solo_dev, chip_num))
+ *val = (s32)((char)rval) + 128;
+ else
+ *val = rval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#if 0
+/*
+ * For audio output volume, the output channel is only 1. In this case we
+ * don't need to offset TW_CHIP_OFFSET_ADDR. The TW_CHIP_OFFSET_ADDR used
+ * is the base address of the techwell chip.
+ */
+void tw2815_Set_AudioOutVol(struct solo_dev *solo_dev, unsigned int u_val)
+{
+ unsigned int val;
+ unsigned int chip_num;
+
+ chip_num = (solo_dev->nr_chans - 1) / 4;
+
+ val = tw_readbyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR,
+ TW_AUDIO_OUTPUT_VOL_ADDR);
+
+ u_val = (val & 0x0f) | (u_val << 4);
+
+ tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_OUTPUT_VOL_ADDR,
+ TW_AUDIO_OUTPUT_VOL_ADDR, u_val);
+}
+#endif
+
+u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch)
+{
+ u8 val;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ val = tw_readbyte(solo_dev, chip_num,
+ TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch));
+
+ return (ch % 2) ? (val >> 4) : (val & 0x0f);
+}
+
+void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val)
+{
+ u8 old_val;
+ u8 chip_num;
+
+ /* Get the right chip and on-chip channel */
+ chip_num = ch / 4;
+ ch %= 4;
+
+ old_val = tw_readbyte(solo_dev, chip_num,
+ TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch));
+
+ val = (old_val & ((ch % 2) ? 0x0f : 0xf0)) |
+ ((ch % 2) ? (val << 4) : val);
+
+ tw_writebyte(solo_dev, chip_num, TW286x_AUDIO_INPUT_GAIN_ADDR(ch),
+ TW_AUDIO_INPUT_GAIN_ADDR(ch), val);
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-tw28.h b/drivers/media/pci/solo6x10/solo6x10-tw28.h
new file mode 100644
index 000000000..4a8ede313
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-tw28.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#ifndef __SOLO6X10_TW28_H
+#define __SOLO6X10_TW28_H
+
+#include "solo6x10.h"
+
+#define TW_NUM_CHIP 4
+#define TW_BASE_ADDR 0x28
+#define TW_CHIP_OFFSET_ADDR(n) (TW_BASE_ADDR + (n))
+
+/* tw2815 */
+#define TW_AV_STAT_ADDR 0x5a
+#define TW_HUE_ADDR(n) (0x07 | ((n) << 4))
+#define TW_SATURATION_ADDR(n) (0x08 | ((n) << 4))
+#define TW_CONTRAST_ADDR(n) (0x09 | ((n) << 4))
+#define TW_BRIGHTNESS_ADDR(n) (0x0a | ((n) << 4))
+#define TW_AUDIO_OUTPUT_VOL_ADDR 0x70
+#define TW_AUDIO_INPUT_GAIN_ADDR(n) (0x60 + ((n > 1) ? 1 : 0))
+
+/* tw286x */
+#define TW286x_AV_STAT_ADDR 0xfd
+#define TW286x_HUE_ADDR(n) (0x06 | ((n) << 4))
+#define TW286x_SATURATIONU_ADDR(n) (0x04 | ((n) << 4))
+#define TW286x_SATURATIONV_ADDR(n) (0x05 | ((n) << 4))
+#define TW286x_CONTRAST_ADDR(n) (0x02 | ((n) << 4))
+#define TW286x_BRIGHTNESS_ADDR(n) (0x01 | ((n) << 4))
+#define TW286x_SHARPNESS(n) (0x03 | ((n) << 4))
+#define TW286x_AUDIO_OUTPUT_VOL_ADDR 0xdf
+#define TW286x_AUDIO_INPUT_GAIN_ADDR(n) (0xD0 + ((n > 1) ? 1 : 0))
+
+int solo_tw28_init(struct solo_dev *solo_dev);
+
+int tw28_set_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 val);
+int tw28_get_ctrl_val(struct solo_dev *solo_dev, u32 ctrl, u8 ch, s32 *val);
+bool tw28_has_sharpness(struct solo_dev *solo_dev, u8 ch);
+
+u8 tw28_get_audio_gain(struct solo_dev *solo_dev, u8 ch);
+void tw28_set_audio_gain(struct solo_dev *solo_dev, u8 ch, u8 val);
+int tw28_get_video_status(struct solo_dev *solo_dev, u8 ch);
+
+#if 0
+unsigned int tw2815_get_audio_status(struct SOLO *solo);
+void tw2815_Set_AudioOutVol(struct SOLO *solo, unsigned int u_val);
+#endif
+
+#endif /* __SOLO6X10_TW28_H */
diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c
new file mode 100644
index 000000000..0adf3d80f
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-v4l2-enc.c
@@ -0,0 +1,1394 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+#include "solo6x10-jpeg.h"
+
+#define MIN_VID_BUFFERS 2
+#define FRAME_BUF_SIZE (400 * 1024)
+#define MP4_QS 16
+#define DMA_ALIGN 4096
+
+/* 6010 M4V */
+static u8 vop_6010_ntsc_d1[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x58, 0x10, 0xf0, 0x71, 0x18, 0x3f,
+};
+
+static u8 vop_6010_ntsc_cif[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x1d, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x2c, 0x10, 0x78, 0x51, 0x18, 0x3f,
+};
+
+static u8 vop_6010_pal_d1[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x58, 0x11, 0x20, 0x71, 0x18, 0x3f,
+};
+
+static u8 vop_6010_pal_cif[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+ 0x02, 0x48, 0x15, 0xc0, 0x00, 0x40, 0x00, 0x40,
+ 0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+ 0x1f, 0x4c, 0x2c, 0x10, 0x90, 0x51, 0x18, 0x3f,
+};
+
+/* 6110 h.264 */
+static u8 vop_6110_ntsc_d1[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x05, 0x81, 0xec, 0x80, 0x00, 0x00,
+ 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00,
+};
+
+static u8 vop_6110_ntsc_cif[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x0b, 0x0f, 0xc8, 0x00, 0x00, 0x00,
+ 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00,
+};
+
+static u8 vop_6110_pal_d1[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x05, 0x80, 0x93, 0x20, 0x00, 0x00,
+ 0x00, 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00,
+};
+
+static u8 vop_6110_pal_cif[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e,
+ 0x9a, 0x74, 0x0b, 0x04, 0xb2, 0x00, 0x00, 0x00,
+ 0x01, 0x68, 0xce, 0x32, 0x28, 0x00, 0x00, 0x00,
+};
+
+typedef __le32 vop_header[16];
+
+struct solo_enc_buf {
+ enum solo_enc_types type;
+ const vop_header *vh;
+ int motion;
+};
+
+static int solo_is_motion_on(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ return (solo_dev->motion_mask >> solo_enc->ch) & 1;
+}
+
+static int solo_motion_detected(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ unsigned long flags;
+ u32 ch_mask = 1 << solo_enc->ch;
+ int ret = 0;
+
+ spin_lock_irqsave(&solo_enc->motion_lock, flags);
+ if (solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS) & ch_mask) {
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, ch_mask);
+ ret = 1;
+ }
+ spin_unlock_irqrestore(&solo_enc->motion_lock, flags);
+
+ return ret;
+}
+
+static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ u32 mask = 1 << solo_enc->ch;
+ unsigned long flags;
+
+ spin_lock_irqsave(&solo_enc->motion_lock, flags);
+
+ if (on)
+ solo_dev->motion_mask |= mask;
+ else
+ solo_dev->motion_mask &= ~mask;
+
+ solo_reg_write(solo_dev, SOLO_VI_MOT_CLEAR, mask);
+
+ solo_reg_write(solo_dev, SOLO_VI_MOT_ADR,
+ SOLO_VI_MOTION_EN(solo_dev->motion_mask) |
+ (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16));
+
+ spin_unlock_irqrestore(&solo_enc->motion_lock, flags);
+}
+
+void solo_update_mode(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ int vop_len;
+ u8 *vop;
+
+ solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0;
+ solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1);
+
+ if (solo_enc->mode == SOLO_ENC_MODE_CIF) {
+ solo_enc->width = solo_dev->video_hsize >> 1;
+ solo_enc->height = solo_dev->video_vsize;
+ if (solo_dev->type == SOLO_DEV_6110) {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6110_ntsc_cif;
+ vop_len = sizeof(vop_6110_ntsc_cif);
+ } else {
+ vop = vop_6110_pal_cif;
+ vop_len = sizeof(vop_6110_pal_cif);
+ }
+ } else {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6010_ntsc_cif;
+ vop_len = sizeof(vop_6010_ntsc_cif);
+ } else {
+ vop = vop_6010_pal_cif;
+ vop_len = sizeof(vop_6010_pal_cif);
+ }
+ }
+ } else {
+ solo_enc->width = solo_dev->video_hsize;
+ solo_enc->height = solo_dev->video_vsize << 1;
+ solo_enc->bw_weight <<= 2;
+ if (solo_dev->type == SOLO_DEV_6110) {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6110_ntsc_d1;
+ vop_len = sizeof(vop_6110_ntsc_d1);
+ } else {
+ vop = vop_6110_pal_d1;
+ vop_len = sizeof(vop_6110_pal_d1);
+ }
+ } else {
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) {
+ vop = vop_6010_ntsc_d1;
+ vop_len = sizeof(vop_6010_ntsc_d1);
+ } else {
+ vop = vop_6010_pal_d1;
+ vop_len = sizeof(vop_6010_pal_d1);
+ }
+ }
+ }
+
+ memcpy(solo_enc->vop, vop, vop_len);
+
+ /* Some fixups for 6010/M4V */
+ if (solo_dev->type == SOLO_DEV_6010) {
+ u16 fps = solo_dev->fps * 1000;
+ u16 interval = solo_enc->interval * 1000;
+
+ vop = solo_enc->vop;
+
+ /* Frame rate and interval */
+ vop[22] = fps >> 4;
+ vop[23] = ((fps << 4) & 0xf0) | 0x0c
+ | ((interval >> 13) & 0x3);
+ vop[24] = (interval >> 5) & 0xff;
+ vop[25] = ((interval << 3) & 0xf8) | 0x04;
+ }
+
+ solo_enc->vop_len = vop_len;
+
+ /* Now handle the jpeg header */
+ vop = solo_enc->jpeg_header;
+ vop[SOF0_START + 5] = 0xff & (solo_enc->height >> 8);
+ vop[SOF0_START + 6] = 0xff & solo_enc->height;
+ vop[SOF0_START + 7] = 0xff & (solo_enc->width >> 8);
+ vop[SOF0_START + 8] = 0xff & solo_enc->width;
+
+ memcpy(vop + DQT_START,
+ jpeg_dqt[solo_g_jpeg_qp(solo_dev, solo_enc->ch)], DQT_LEN);
+}
+
+static int solo_enc_on(struct solo_enc_dev *solo_enc)
+{
+ u8 ch = solo_enc->ch;
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ u8 interval;
+
+ solo_update_mode(solo_enc);
+
+ /* Make sure to do a bandwidth check */
+ if (solo_enc->bw_weight > solo_dev->enc_bw_remain)
+ return -EBUSY;
+ solo_enc->sequence = 0;
+ solo_dev->enc_bw_remain -= solo_enc->bw_weight;
+
+ if (solo_enc->type == SOLO_ENC_TYPE_EXT)
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1);
+
+ /* Disable all encoding for this channel */
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), 0);
+
+ /* Common for both std and ext encoding */
+ solo_reg_write(solo_dev, SOLO_VE_CH_INTL(ch),
+ solo_enc->interlaced ? 1 : 0);
+
+ if (solo_enc->interlaced)
+ interval = solo_enc->interval - 1;
+ else
+ interval = solo_enc->interval;
+
+ /* Standard encoding only */
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), solo_enc->gop);
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP(ch), solo_enc->qp);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_INTV(ch), interval);
+
+ /* Extended encoding only */
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(ch), solo_enc->gop);
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(ch), solo_enc->qp);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_INTV_E(ch), interval);
+
+ /* Enables the standard encoder */
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(ch), solo_enc->mode);
+
+ return 0;
+}
+
+static void solo_enc_off(struct solo_enc_dev *solo_enc)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ solo_dev->enc_bw_remain += solo_enc->bw_weight;
+
+ solo_reg_write(solo_dev, SOLO_CAP_CH_SCALE(solo_enc->ch), 0);
+ solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(solo_enc->ch), 0);
+}
+
+static int enc_get_mpeg_dma(struct solo_dev *solo_dev, dma_addr_t dma,
+ unsigned int off, unsigned int size)
+{
+ int ret;
+
+ if (off > SOLO_MP4E_EXT_SIZE(solo_dev))
+ return -EINVAL;
+
+ /* Single shot */
+ if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) {
+ return solo_p2m_dma_t(solo_dev, 0, dma,
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off, size,
+ 0, 0);
+ }
+
+ /* Buffer wrap */
+ ret = solo_p2m_dma_t(solo_dev, 0, dma,
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off,
+ SOLO_MP4E_EXT_SIZE(solo_dev) - off, 0, 0);
+
+ if (!ret) {
+ ret = solo_p2m_dma_t(solo_dev, 0,
+ dma + SOLO_MP4E_EXT_SIZE(solo_dev) - off,
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ size + off - SOLO_MP4E_EXT_SIZE(solo_dev), 0, 0);
+ }
+
+ return ret;
+}
+
+/* Build a descriptor queue out of an SG list and send it to the P2M for
+ * processing. */
+static int solo_send_desc(struct solo_enc_dev *solo_enc, int skip,
+ struct sg_table *vbuf, int off, int size,
+ unsigned int base, unsigned int base_size)
+{
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct scatterlist *sg;
+ int i;
+ int ret;
+
+ if (WARN_ON_ONCE(size > FRAME_BUF_SIZE))
+ return -EINVAL;
+
+ solo_enc->desc_count = 1;
+
+ for_each_sg(vbuf->sgl, sg, vbuf->nents, i) {
+ struct solo_p2m_desc *desc;
+ dma_addr_t dma;
+ int len;
+ int left = base_size - off;
+
+ desc = &solo_enc->desc_items[solo_enc->desc_count++];
+ dma = sg_dma_address(sg);
+ len = sg_dma_len(sg);
+
+ /* We assume this is smaller than the scatter size */
+ BUG_ON(skip >= len);
+ if (skip) {
+ len -= skip;
+ dma += skip;
+ size -= skip;
+ skip = 0;
+ }
+
+ len = min(len, size);
+
+ if (len <= left) {
+ /* Single descriptor */
+ solo_p2m_fill_desc(desc, 0, dma, base + off,
+ len, 0, 0);
+ } else {
+ /* Buffer wrap */
+ /* XXX: Do these as separate DMA requests, to avoid
+ timeout errors triggered by awkwardly sized
+ descriptors. See
+ <https://github.com/bluecherrydvr/solo6x10/issues/8>
+ */
+ ret = solo_p2m_dma_t(solo_dev, 0, dma, base + off,
+ left, 0, 0);
+ if (ret)
+ return ret;
+
+ ret = solo_p2m_dma_t(solo_dev, 0, dma + left, base,
+ len - left, 0, 0);
+ if (ret)
+ return ret;
+
+ solo_enc->desc_count--;
+ }
+
+ size -= len;
+ if (size <= 0)
+ break;
+
+ off += len;
+ if (off >= base_size)
+ off -= base_size;
+
+ /* Because we may use two descriptors per loop */
+ if (solo_enc->desc_count >= (solo_enc->desc_nelts - 1)) {
+ ret = solo_p2m_dma_desc(solo_dev, solo_enc->desc_items,
+ solo_enc->desc_dma,
+ solo_enc->desc_count - 1);
+ if (ret)
+ return ret;
+ solo_enc->desc_count = 1;
+ }
+ }
+
+ if (solo_enc->desc_count <= 1)
+ return 0;
+
+ return solo_p2m_dma_desc(solo_dev, solo_enc->desc_items,
+ solo_enc->desc_dma, solo_enc->desc_count - 1);
+}
+
+/* Extract values from VOP header - VE_STATUSxx */
+static inline __always_unused int vop_interlaced(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 30) & 1;
+}
+
+static inline __always_unused u8 vop_channel(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 24) & 0x1F;
+}
+
+static inline u8 vop_type(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[0]) >> 22) & 3;
+}
+
+static inline u32 vop_mpeg_size(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[0]) & 0xFFFFF;
+}
+
+static inline u8 __always_unused vop_hsize(const vop_header *vh)
+{
+ return (__le32_to_cpu((*vh)[1]) >> 8) & 0xFF;
+}
+
+static inline u8 __always_unused vop_vsize(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[1]) & 0xFF;
+}
+
+static inline u32 vop_mpeg_offset(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[2]);
+}
+
+static inline u32 vop_jpeg_offset(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[3]);
+}
+
+static inline u32 vop_jpeg_size(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[4]) & 0xFFFFF;
+}
+
+static inline u32 __always_unused vop_sec(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[5]);
+}
+
+static inline __always_unused u32 vop_usec(const vop_header *vh)
+{
+ return __le32_to_cpu((*vh)[6]);
+}
+
+static int solo_fill_jpeg(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, const vop_header *vh)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
+ int frame_size;
+
+ vbuf->flags |= V4L2_BUF_FLAG_KEYFRAME;
+
+ if (vb2_plane_size(vb, 0) < vop_jpeg_size(vh) + solo_enc->jpeg_len)
+ return -EIO;
+
+ frame_size = ALIGN(vop_jpeg_size(vh) + solo_enc->jpeg_len, DMA_ALIGN);
+ vb2_set_plane_payload(vb, 0, vop_jpeg_size(vh) + solo_enc->jpeg_len);
+
+ return solo_send_desc(solo_enc, solo_enc->jpeg_len, sgt,
+ vop_jpeg_offset(vh) - SOLO_JPEG_EXT_ADDR(solo_dev),
+ frame_size, SOLO_JPEG_EXT_ADDR(solo_dev),
+ SOLO_JPEG_EXT_SIZE(solo_dev));
+}
+
+static int solo_fill_mpeg(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, const vop_header *vh)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
+ int frame_off, frame_size;
+ int skip = 0;
+
+ if (vb2_plane_size(vb, 0) < vop_mpeg_size(vh))
+ return -EIO;
+
+ /* If this is a key frame, add extra header */
+ vbuf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME |
+ V4L2_BUF_FLAG_BFRAME);
+ if (!vop_type(vh)) {
+ skip = solo_enc->vop_len;
+ vbuf->flags |= V4L2_BUF_FLAG_KEYFRAME;
+ vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh) +
+ solo_enc->vop_len);
+ } else {
+ vbuf->flags |= V4L2_BUF_FLAG_PFRAME;
+ vb2_set_plane_payload(vb, 0, vop_mpeg_size(vh));
+ }
+
+ /* Now get the actual mpeg payload */
+ frame_off = (vop_mpeg_offset(vh) - SOLO_MP4E_EXT_ADDR(solo_dev) +
+ sizeof(*vh)) % SOLO_MP4E_EXT_SIZE(solo_dev);
+ frame_size = ALIGN(vop_mpeg_size(vh) + skip, DMA_ALIGN);
+
+ return solo_send_desc(solo_enc, skip, sgt, frame_off, frame_size,
+ SOLO_MP4E_EXT_ADDR(solo_dev),
+ SOLO_MP4E_EXT_SIZE(solo_dev));
+}
+
+static int solo_enc_fillbuf(struct solo_enc_dev *solo_enc,
+ struct vb2_buffer *vb, struct solo_enc_buf *enc_buf)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ const vop_header *vh = enc_buf->vh;
+ int ret;
+
+ switch (solo_enc->fmt) {
+ case V4L2_PIX_FMT_MPEG4:
+ case V4L2_PIX_FMT_H264:
+ ret = solo_fill_mpeg(solo_enc, vb, vh);
+ break;
+ default: /* V4L2_PIX_FMT_MJPEG */
+ ret = solo_fill_jpeg(solo_enc, vb, vh);
+ break;
+ }
+
+ if (!ret) {
+ vbuf->sequence = solo_enc->sequence++;
+ vb->timestamp = ktime_get_ns();
+
+ /* Check for motion flags */
+ if (solo_is_motion_on(solo_enc) && enc_buf->motion) {
+ struct v4l2_event ev = {
+ .type = V4L2_EVENT_MOTION_DET,
+ .u.motion_det = {
+ .flags
+ = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ,
+ .frame_sequence = vbuf->sequence,
+ .region_mask = enc_buf->motion ? 1 : 0,
+ },
+ };
+
+ v4l2_event_queue(solo_enc->vfd, &ev);
+ }
+ }
+
+ vb2_buffer_done(vb, ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+
+ return ret;
+}
+
+static void solo_enc_handle_one(struct solo_enc_dev *solo_enc,
+ struct solo_enc_buf *enc_buf)
+{
+ struct solo_vb2_buf *vb;
+ unsigned long flags;
+
+ mutex_lock(&solo_enc->lock);
+ if (solo_enc->type != enc_buf->type)
+ goto unlock;
+
+ spin_lock_irqsave(&solo_enc->av_lock, flags);
+ if (list_empty(&solo_enc->vidq_active)) {
+ spin_unlock_irqrestore(&solo_enc->av_lock, flags);
+ goto unlock;
+ }
+ vb = list_first_entry(&solo_enc->vidq_active, struct solo_vb2_buf,
+ list);
+ list_del(&vb->list);
+ spin_unlock_irqrestore(&solo_enc->av_lock, flags);
+
+ solo_enc_fillbuf(solo_enc, &vb->vb.vb2_buf, enc_buf);
+unlock:
+ mutex_unlock(&solo_enc->lock);
+}
+
+void solo_enc_v4l2_isr(struct solo_dev *solo_dev)
+{
+ wake_up_interruptible_all(&solo_dev->ring_thread_wait);
+}
+
+static void solo_handle_ring(struct solo_dev *solo_dev)
+{
+ for (;;) {
+ struct solo_enc_dev *solo_enc;
+ struct solo_enc_buf enc_buf;
+ u32 mpeg_current, off;
+ u8 ch;
+ u8 cur_q;
+
+ /* Check if the hardware has any new ones in the queue */
+ cur_q = solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xff;
+ if (cur_q == solo_dev->enc_idx)
+ break;
+
+ mpeg_current = solo_reg_read(solo_dev,
+ SOLO_VE_MPEG4_QUE(solo_dev->enc_idx));
+ solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS;
+
+ ch = (mpeg_current >> 24) & 0x1f;
+ off = mpeg_current & 0x00ffffff;
+
+ if (ch >= SOLO_MAX_CHANNELS) {
+ ch -= SOLO_MAX_CHANNELS;
+ enc_buf.type = SOLO_ENC_TYPE_EXT;
+ } else
+ enc_buf.type = SOLO_ENC_TYPE_STD;
+
+ solo_enc = solo_dev->v4l2_enc[ch];
+ if (solo_enc == NULL) {
+ dev_err(&solo_dev->pdev->dev,
+ "Got spurious packet for channel %d\n", ch);
+ continue;
+ }
+
+ /* FAIL... */
+ if (enc_get_mpeg_dma(solo_dev, solo_dev->vh_dma, off,
+ sizeof(vop_header)))
+ continue;
+
+ enc_buf.vh = solo_dev->vh_buf;
+
+ /* Sanity check */
+ if (vop_mpeg_offset(enc_buf.vh) !=
+ SOLO_MP4E_EXT_ADDR(solo_dev) + off)
+ continue;
+
+ if (solo_motion_detected(solo_enc))
+ enc_buf.motion = 1;
+ else
+ enc_buf.motion = 0;
+
+ solo_enc_handle_one(solo_enc, &enc_buf);
+ }
+}
+
+static int solo_ring_thread(void *data)
+{
+ struct solo_dev *solo_dev = data;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_freezable();
+ add_wait_queue(&solo_dev->ring_thread_wait, &wait);
+
+ for (;;) {
+ long timeout = schedule_timeout_interruptible(HZ);
+
+ if (timeout == -ERESTARTSYS || kthread_should_stop())
+ break;
+ solo_handle_ring(solo_dev);
+ try_to_freeze();
+ }
+
+ remove_wait_queue(&solo_dev->ring_thread_wait, &wait);
+
+ return 0;
+}
+
+static int solo_enc_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers,
+ unsigned int *num_planes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ sizes[0] = FRAME_BUF_SIZE;
+ *num_planes = 1;
+
+ if (*num_buffers < MIN_VID_BUFFERS)
+ *num_buffers = MIN_VID_BUFFERS;
+
+ return 0;
+}
+
+static void solo_enc_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vq);
+ struct solo_vb2_buf *solo_vb =
+ container_of(vbuf, struct solo_vb2_buf, vb);
+
+ spin_lock(&solo_enc->av_lock);
+ list_add_tail(&solo_vb->list, &solo_enc->vidq_active);
+ spin_unlock(&solo_enc->av_lock);
+}
+
+static int solo_ring_start(struct solo_dev *solo_dev)
+{
+ solo_dev->ring_thread = kthread_run(solo_ring_thread, solo_dev,
+ SOLO6X10_NAME "_ring");
+ if (IS_ERR(solo_dev->ring_thread)) {
+ int err = PTR_ERR(solo_dev->ring_thread);
+
+ solo_dev->ring_thread = NULL;
+ return err;
+ }
+
+ solo_irq_on(solo_dev, SOLO_IRQ_ENCODER);
+
+ return 0;
+}
+
+static void solo_ring_stop(struct solo_dev *solo_dev)
+{
+ if (solo_dev->ring_thread) {
+ kthread_stop(solo_dev->ring_thread);
+ solo_dev->ring_thread = NULL;
+ }
+
+ solo_irq_off(solo_dev, SOLO_IRQ_ENCODER);
+}
+
+static int solo_enc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q);
+
+ return solo_enc_on(solo_enc);
+}
+
+static void solo_enc_stop_streaming(struct vb2_queue *q)
+{
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(q);
+ unsigned long flags;
+
+ spin_lock_irqsave(&solo_enc->av_lock, flags);
+ solo_enc_off(solo_enc);
+ while (!list_empty(&solo_enc->vidq_active)) {
+ struct solo_vb2_buf *buf = list_entry(
+ solo_enc->vidq_active.next,
+ struct solo_vb2_buf, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock_irqrestore(&solo_enc->av_lock, flags);
+}
+
+static void solo_enc_buf_finish(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct solo_enc_dev *solo_enc = vb2_get_drv_priv(vb->vb2_queue);
+ struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0);
+
+ switch (solo_enc->fmt) {
+ case V4L2_PIX_FMT_MPEG4:
+ case V4L2_PIX_FMT_H264:
+ if (vbuf->flags & V4L2_BUF_FLAG_KEYFRAME)
+ sg_copy_from_buffer(sgt->sgl, sgt->nents,
+ solo_enc->vop, solo_enc->vop_len);
+ break;
+ default: /* V4L2_PIX_FMT_MJPEG */
+ sg_copy_from_buffer(sgt->sgl, sgt->nents,
+ solo_enc->jpeg_header, solo_enc->jpeg_len);
+ break;
+ }
+}
+
+static const struct vb2_ops solo_enc_video_qops = {
+ .queue_setup = solo_enc_queue_setup,
+ .buf_queue = solo_enc_buf_queue,
+ .buf_finish = solo_enc_buf_finish,
+ .start_streaming = solo_enc_start_streaming,
+ .stop_streaming = solo_enc_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int solo_enc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+
+ strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver));
+ snprintf(cap->card, sizeof(cap->card), "Softlogic 6x10 Enc %d",
+ solo_enc->ch);
+ return 0;
+}
+
+static int solo_enc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (input->index)
+ return -EINVAL;
+
+ snprintf(input->name, sizeof(input->name), "Encoder %d",
+ solo_enc->ch + 1);
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->std = solo_enc->vfd->tvnorms;
+
+ if (!tw28_get_video_status(solo_dev, solo_enc->ch))
+ input->status = V4L2_IN_ST_NO_SIGNAL;
+
+ return 0;
+}
+
+static int solo_enc_set_input(struct file *file, void *priv,
+ unsigned int index)
+{
+ if (index)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int solo_enc_get_input(struct file *file, void *priv,
+ unsigned int *index)
+{
+ *index = 0;
+
+ return 0;
+}
+
+static int solo_enc_enum_fmt_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ int dev_type = solo_enc->solo_dev->type;
+
+ switch (f->index) {
+ case 0:
+ switch (dev_type) {
+ case SOLO_DEV_6010:
+ f->pixelformat = V4L2_PIX_FMT_MPEG4;
+ break;
+ case SOLO_DEV_6110:
+ f->pixelformat = V4L2_PIX_FMT_H264;
+ break;
+ }
+ break;
+ case 1:
+ f->pixelformat = V4L2_PIX_FMT_MJPEG;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static inline int solo_valid_pixfmt(u32 pixfmt, int dev_type)
+{
+ return (pixfmt == V4L2_PIX_FMT_H264 && dev_type == SOLO_DEV_6110)
+ || (pixfmt == V4L2_PIX_FMT_MPEG4 && dev_type == SOLO_DEV_6010)
+ || pixfmt == V4L2_PIX_FMT_MJPEG ? 0 : -EINVAL;
+}
+
+static int solo_enc_try_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ if (solo_valid_pixfmt(pix->pixelformat, solo_dev->type))
+ return -EINVAL;
+
+ if (pix->width < solo_dev->video_hsize ||
+ pix->height < solo_dev->video_vsize << 1) {
+ /* Default to CIF 1/2 size */
+ pix->width = solo_dev->video_hsize >> 1;
+ pix->height = solo_dev->video_vsize;
+ } else {
+ /* Full frame */
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_dev->video_vsize << 1;
+ }
+
+ switch (pix->field) {
+ case V4L2_FIELD_NONE:
+ case V4L2_FIELD_INTERLACED:
+ break;
+ case V4L2_FIELD_ANY:
+ default:
+ pix->field = V4L2_FIELD_INTERLACED;
+ break;
+ }
+
+ /* Just set these */
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->sizeimage = FRAME_BUF_SIZE;
+ pix->bytesperline = 0;
+
+ return 0;
+}
+
+static int solo_enc_set_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int ret;
+
+ if (vb2_is_busy(&solo_enc->vidq))
+ return -EBUSY;
+
+ ret = solo_enc_try_fmt_cap(file, priv, f);
+ if (ret)
+ return ret;
+
+ if (pix->width == solo_dev->video_hsize)
+ solo_enc->mode = SOLO_ENC_MODE_D1;
+ else
+ solo_enc->mode = SOLO_ENC_MODE_CIF;
+
+ /* This does not change the encoder at all */
+ solo_enc->fmt = pix->pixelformat;
+
+ /*
+ * More information is needed about these 'extended' types. As far
+ * as I can tell these are basically additional video streams with
+ * different MPEG encoding attributes that can run in parallel with
+ * the main stream. If so, then this should be implemented as a
+ * second video node. Abusing priv like this is certainly not the
+ * right approach.
+ if (pix->priv)
+ solo_enc->type = SOLO_ENC_TYPE_EXT;
+ */
+ solo_update_mode(solo_enc);
+ return 0;
+}
+
+static int solo_enc_get_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ pix->width = solo_enc->width;
+ pix->height = solo_enc->height;
+ pix->pixelformat = solo_enc->fmt;
+ pix->field = solo_enc->interlaced ? V4L2_FIELD_INTERLACED :
+ V4L2_FIELD_NONE;
+ pix->sizeimage = FRAME_BUF_SIZE;
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+ return 0;
+}
+
+static int solo_enc_g_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+ *i = V4L2_STD_NTSC_M;
+ else
+ *i = V4L2_STD_PAL;
+ return 0;
+}
+
+static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+
+ return solo_set_video_type(solo_enc->solo_dev, std & V4L2_STD_625_50);
+}
+
+static int solo_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_valid_pixfmt(fsize->pixel_format, solo_dev->type))
+ return -EINVAL;
+
+ switch (fsize->index) {
+ case 0:
+ fsize->discrete.width = solo_dev->video_hsize >> 1;
+ fsize->discrete.height = solo_dev->video_vsize;
+ break;
+ case 1:
+ fsize->discrete.width = solo_dev->video_hsize;
+ fsize->discrete.height = solo_dev->video_vsize << 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+
+ return 0;
+}
+
+static int solo_enum_frameintervals(struct file *file, void *priv,
+ struct v4l2_frmivalenum *fintv)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+
+ if (solo_valid_pixfmt(fintv->pixel_format, solo_dev->type))
+ return -EINVAL;
+ if (fintv->index)
+ return -EINVAL;
+ if ((fintv->width != solo_dev->video_hsize >> 1 ||
+ fintv->height != solo_dev->video_vsize) &&
+ (fintv->width != solo_dev->video_hsize ||
+ fintv->height != solo_dev->video_vsize << 1))
+ return -EINVAL;
+
+ fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+ fintv->stepwise.min.numerator = 1;
+ fintv->stepwise.min.denominator = solo_dev->fps;
+
+ fintv->stepwise.max.numerator = 15;
+ fintv->stepwise.max.denominator = solo_dev->fps;
+
+ fintv->stepwise.step.numerator = 1;
+ fintv->stepwise.step.denominator = solo_dev->fps;
+
+ return 0;
+}
+
+static int solo_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_captureparm *cp = &sp->parm.capture;
+
+ cp->capability = V4L2_CAP_TIMEPERFRAME;
+ cp->timeperframe.numerator = solo_enc->interval;
+ cp->timeperframe.denominator = solo_enc->solo_dev->fps;
+ cp->capturemode = 0;
+ /* XXX: Shouldn't we be able to get/set this from vb2? */
+ cp->readbuffers = 2;
+
+ return 0;
+}
+
+static inline int calc_interval(u8 fps, u32 n, u32 d)
+{
+ if (!n || !d)
+ return 1;
+ if (d == fps)
+ return n;
+ n *= fps;
+ return min(15U, n / d + (n % d >= (fps >> 1)));
+}
+
+static int solo_s_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *sp)
+{
+ struct solo_enc_dev *solo_enc = video_drvdata(file);
+ struct v4l2_fract *t = &sp->parm.capture.timeperframe;
+ u8 fps = solo_enc->solo_dev->fps;
+
+ if (vb2_is_streaming(&solo_enc->vidq))
+ return -EBUSY;
+
+ solo_enc->interval = calc_interval(fps, t->numerator, t->denominator);
+ solo_update_mode(solo_enc);
+ return solo_g_parm(file, priv, sp);
+}
+
+static int solo_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct solo_enc_dev *solo_enc =
+ container_of(ctrl->handler, struct solo_enc_dev, hdl);
+ struct solo_dev *solo_dev = solo_enc->solo_dev;
+ int err;
+
+ switch (ctrl->id) {
+ case V4L2_CID_BRIGHTNESS:
+ case V4L2_CID_CONTRAST:
+ case V4L2_CID_SATURATION:
+ case V4L2_CID_HUE:
+ case V4L2_CID_SHARPNESS:
+ return tw28_set_ctrl_val(solo_dev, ctrl->id, solo_enc->ch,
+ ctrl->val);
+ case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+ solo_enc->gop = ctrl->val;
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP(solo_enc->ch), solo_enc->gop);
+ solo_reg_write(solo_dev, SOLO_VE_CH_GOP_E(solo_enc->ch), solo_enc->gop);
+ return 0;
+ case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+ solo_enc->qp = ctrl->val;
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP(solo_enc->ch), solo_enc->qp);
+ solo_reg_write(solo_dev, SOLO_VE_CH_QP_E(solo_enc->ch), solo_enc->qp);
+ return 0;
+ case V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD:
+ solo_enc->motion_thresh = ctrl->val << 8;
+ if (!solo_enc->motion_global || !solo_enc->motion_enabled)
+ return 0;
+ return solo_set_motion_threshold(solo_dev, solo_enc->ch,
+ solo_enc->motion_thresh);
+ case V4L2_CID_DETECT_MD_MODE:
+ solo_enc->motion_global = ctrl->val == V4L2_DETECT_MD_MODE_GLOBAL;
+ solo_enc->motion_enabled = ctrl->val > V4L2_DETECT_MD_MODE_DISABLED;
+ if (ctrl->val) {
+ if (solo_enc->motion_global)
+ err = solo_set_motion_threshold(solo_dev, solo_enc->ch,
+ solo_enc->motion_thresh);
+ else
+ err = solo_set_motion_block(solo_dev, solo_enc->ch,
+ solo_enc->md_thresholds->p_cur.p_u16);
+ if (err)
+ return err;
+ }
+ solo_motion_toggle(solo_enc, ctrl->val);
+ return 0;
+ case V4L2_CID_DETECT_MD_THRESHOLD_GRID:
+ if (solo_enc->motion_enabled && !solo_enc->motion_global)
+ return solo_set_motion_block(solo_dev, solo_enc->ch,
+ solo_enc->md_thresholds->p_new.p_u16);
+ break;
+ case V4L2_CID_OSD_TEXT:
+ strscpy(solo_enc->osd_text, ctrl->p_new.p_char,
+ sizeof(solo_enc->osd_text));
+ return solo_osd_print(solo_enc);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int solo_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+
+ switch (sub->type) {
+ case V4L2_EVENT_MOTION_DET:
+ /* Allow for up to 30 events (1 second for NTSC) to be
+ * stored. */
+ return v4l2_event_subscribe(fh, sub, 30, NULL);
+ default:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ }
+}
+
+static const struct v4l2_file_operations solo_enc_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops solo_enc_ioctl_ops = {
+ .vidioc_querycap = solo_enc_querycap,
+ .vidioc_s_std = solo_enc_s_std,
+ .vidioc_g_std = solo_enc_g_std,
+ /* Input callbacks */
+ .vidioc_enum_input = solo_enc_enum_input,
+ .vidioc_s_input = solo_enc_set_input,
+ .vidioc_g_input = solo_enc_get_input,
+ /* Video capture format callbacks */
+ .vidioc_enum_fmt_vid_cap = solo_enc_enum_fmt_cap,
+ .vidioc_try_fmt_vid_cap = solo_enc_try_fmt_cap,
+ .vidioc_s_fmt_vid_cap = solo_enc_set_fmt_cap,
+ .vidioc_g_fmt_vid_cap = solo_enc_get_fmt_cap,
+ /* Streaming I/O */
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ /* Frame size and interval */
+ .vidioc_enum_framesizes = solo_enum_framesizes,
+ .vidioc_enum_frameintervals = solo_enum_frameintervals,
+ /* Video capture parameters */
+ .vidioc_s_parm = solo_s_parm,
+ .vidioc_g_parm = solo_g_parm,
+ /* Logging and events */
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = solo_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device solo_enc_template = {
+ .name = SOLO6X10_NAME,
+ .fops = &solo_enc_fops,
+ .ioctl_ops = &solo_enc_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL,
+ .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING,
+};
+
+static const struct v4l2_ctrl_ops solo_ctrl_ops = {
+ .s_ctrl = solo_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config solo_osd_text_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_OSD_TEXT,
+ .name = "OSD Text",
+ .type = V4L2_CTRL_TYPE_STRING,
+ .max = OSD_TEXT_MAX,
+ .step = 1,
+};
+
+/* Motion Detection Threshold matrix */
+static const struct v4l2_ctrl_config solo_md_thresholds = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_DETECT_MD_THRESHOLD_GRID,
+ .dims = { SOLO_MOTION_SZ, SOLO_MOTION_SZ },
+ .def = SOLO_DEF_MOT_THRESH,
+ .max = 65535,
+ .step = 1,
+};
+
+static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev,
+ u8 ch, unsigned nr)
+{
+ struct solo_enc_dev *solo_enc;
+ struct v4l2_ctrl_handler *hdl;
+ int ret;
+
+ solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL);
+ if (!solo_enc)
+ return ERR_PTR(-ENOMEM);
+
+ hdl = &solo_enc->hdl;
+ v4l2_ctrl_handler_init(hdl, 10);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_CONTRAST, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_SATURATION, 0, 255, 1, 128);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_HUE, 0, 255, 1, 128);
+ if (tw28_has_sharpness(solo_dev, ch))
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_SHARPNESS, 0, 15, 1, 0);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 255, 1, solo_dev->fps);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 0, 31, 1, SOLO_DEFAULT_QP);
+ v4l2_ctrl_new_std_menu(hdl, &solo_ctrl_ops,
+ V4L2_CID_DETECT_MD_MODE,
+ V4L2_DETECT_MD_MODE_THRESHOLD_GRID, 0,
+ V4L2_DETECT_MD_MODE_DISABLED);
+ v4l2_ctrl_new_std(hdl, &solo_ctrl_ops,
+ V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, 0, 0xff, 1,
+ SOLO_DEF_MOT_THRESH >> 8);
+ v4l2_ctrl_new_custom(hdl, &solo_osd_text_ctrl, NULL);
+ solo_enc->md_thresholds =
+ v4l2_ctrl_new_custom(hdl, &solo_md_thresholds, NULL);
+ if (hdl->error) {
+ ret = hdl->error;
+ goto hdl_free;
+ }
+
+ solo_enc->solo_dev = solo_dev;
+ solo_enc->ch = ch;
+ mutex_init(&solo_enc->lock);
+ spin_lock_init(&solo_enc->av_lock);
+ INIT_LIST_HEAD(&solo_enc->vidq_active);
+ solo_enc->fmt = (solo_dev->type == SOLO_DEV_6010) ?
+ V4L2_PIX_FMT_MPEG4 : V4L2_PIX_FMT_H264;
+ solo_enc->type = SOLO_ENC_TYPE_STD;
+
+ solo_enc->qp = SOLO_DEFAULT_QP;
+ solo_enc->gop = solo_dev->fps;
+ solo_enc->interval = 1;
+ solo_enc->mode = SOLO_ENC_MODE_CIF;
+ solo_enc->motion_global = true;
+ solo_enc->motion_thresh = SOLO_DEF_MOT_THRESH;
+ solo_enc->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ solo_enc->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ solo_enc->vidq.ops = &solo_enc_video_qops;
+ solo_enc->vidq.mem_ops = &vb2_dma_sg_memops;
+ solo_enc->vidq.drv_priv = solo_enc;
+ solo_enc->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM;
+ solo_enc->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ solo_enc->vidq.buf_struct_size = sizeof(struct solo_vb2_buf);
+ solo_enc->vidq.lock = &solo_enc->lock;
+ solo_enc->vidq.dev = &solo_dev->pdev->dev;
+ ret = vb2_queue_init(&solo_enc->vidq);
+ if (ret)
+ goto hdl_free;
+ solo_update_mode(solo_enc);
+
+ spin_lock_init(&solo_enc->motion_lock);
+
+ /* Initialize this per encoder */
+ solo_enc->jpeg_len = sizeof(jpeg_header);
+ memcpy(solo_enc->jpeg_header, jpeg_header, solo_enc->jpeg_len);
+
+ solo_enc->desc_nelts = 32;
+ solo_enc->desc_items = dma_alloc_coherent(&solo_dev->pdev->dev,
+ sizeof(struct solo_p2m_desc) *
+ solo_enc->desc_nelts,
+ &solo_enc->desc_dma,
+ GFP_KERNEL);
+ ret = -ENOMEM;
+ if (solo_enc->desc_items == NULL)
+ goto hdl_free;
+
+ solo_enc->vfd = video_device_alloc();
+ if (!solo_enc->vfd)
+ goto pci_free;
+
+ *solo_enc->vfd = solo_enc_template;
+ solo_enc->vfd->v4l2_dev = &solo_dev->v4l2_dev;
+ solo_enc->vfd->ctrl_handler = hdl;
+ solo_enc->vfd->queue = &solo_enc->vidq;
+ solo_enc->vfd->lock = &solo_enc->lock;
+ video_set_drvdata(solo_enc->vfd, solo_enc);
+ ret = video_register_device(solo_enc->vfd, VFL_TYPE_VIDEO, nr);
+ if (ret < 0)
+ goto vdev_release;
+
+ snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name),
+ "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num,
+ solo_enc->vfd->num);
+
+ return solo_enc;
+
+vdev_release:
+ video_device_release(solo_enc->vfd);
+pci_free:
+ dma_free_coherent(&solo_enc->solo_dev->pdev->dev,
+ sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts,
+ solo_enc->desc_items, solo_enc->desc_dma);
+hdl_free:
+ v4l2_ctrl_handler_free(hdl);
+ kfree(solo_enc);
+ return ERR_PTR(ret);
+}
+
+static void solo_enc_free(struct solo_enc_dev *solo_enc)
+{
+ if (solo_enc == NULL)
+ return;
+
+ dma_free_coherent(&solo_enc->solo_dev->pdev->dev,
+ sizeof(struct solo_p2m_desc) * solo_enc->desc_nelts,
+ solo_enc->desc_items, solo_enc->desc_dma);
+ video_unregister_device(solo_enc->vfd);
+ v4l2_ctrl_handler_free(&solo_enc->hdl);
+ kfree(solo_enc);
+}
+
+int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr)
+{
+ int i;
+
+ init_waitqueue_head(&solo_dev->ring_thread_wait);
+
+ solo_dev->vh_size = sizeof(vop_header);
+ solo_dev->vh_buf = dma_alloc_coherent(&solo_dev->pdev->dev,
+ solo_dev->vh_size,
+ &solo_dev->vh_dma, GFP_KERNEL);
+ if (solo_dev->vh_buf == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i, nr);
+ if (IS_ERR(solo_dev->v4l2_enc[i]))
+ break;
+ }
+
+ if (i != solo_dev->nr_chans) {
+ int ret = PTR_ERR(solo_dev->v4l2_enc[i]);
+
+ while (i--)
+ solo_enc_free(solo_dev->v4l2_enc[i]);
+ dma_free_coherent(&solo_dev->pdev->dev, solo_dev->vh_size,
+ solo_dev->vh_buf, solo_dev->vh_dma);
+ solo_dev->vh_buf = NULL;
+ return ret;
+ }
+
+ if (solo_dev->type == SOLO_DEV_6010)
+ solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4;
+ else
+ solo_dev->enc_bw_remain = solo_dev->fps * 4 * 5;
+
+ dev_info(&solo_dev->pdev->dev, "Encoders as /dev/video%d-%d\n",
+ solo_dev->v4l2_enc[0]->vfd->num,
+ solo_dev->v4l2_enc[solo_dev->nr_chans - 1]->vfd->num);
+
+ return solo_ring_start(solo_dev);
+}
+
+void solo_enc_v4l2_exit(struct solo_dev *solo_dev)
+{
+ int i;
+
+ solo_ring_stop(solo_dev);
+
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_enc_free(solo_dev->v4l2_enc[i]);
+
+ if (solo_dev->vh_buf)
+ dma_free_coherent(&solo_dev->pdev->dev, solo_dev->vh_size,
+ solo_dev->vh_buf, solo_dev->vh_dma);
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10-v4l2.c b/drivers/media/pci/solo6x10/solo6x10-v4l2.c
new file mode 100644
index 000000000..e18cc41fc
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10-v4l2.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "solo6x10.h"
+#include "solo6x10-tw28.h"
+
+/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */
+#define SOLO_HW_BPL 2048
+#define solo_vlines(__solo) (__solo->video_vsize * 2)
+#define solo_image_size(__solo) (solo_bytesperline(__solo) * \
+ solo_vlines(__solo))
+#define solo_bytesperline(__solo) (__solo->video_hsize * 2)
+
+#define MIN_VID_BUFFERS 2
+
+static inline void erase_on(struct solo_dev *solo_dev)
+{
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
+ solo_dev->erasing = 1;
+ solo_dev->frame_blank = 0;
+}
+
+static inline int erase_off(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->erasing)
+ return 0;
+
+ /* First time around, assert erase off */
+ if (!solo_dev->frame_blank)
+ solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0);
+ /* Keep the erasing flag on for 8 frames minimum */
+ if (solo_dev->frame_blank++ >= 8)
+ solo_dev->erasing = 0;
+
+ return 1;
+}
+
+void solo_video_in_isr(struct solo_dev *solo_dev)
+{
+ wake_up_interruptible_all(&solo_dev->disp_thread_wait);
+}
+
+static void solo_win_setup(struct solo_dev *solo_dev, u8 ch,
+ int sx, int sy, int ex, int ey, int scale)
+{
+ if (ch >= solo_dev->nr_chans)
+ return;
+
+ /* Here, we just keep window/channel the same */
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch),
+ SOLO_VI_WIN_CHANNEL(ch) |
+ SOLO_VI_WIN_SX(sx) |
+ SOLO_VI_WIN_EX(ex) |
+ SOLO_VI_WIN_SCALE(scale));
+
+ solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch),
+ SOLO_VI_WIN_SY(sy) |
+ SOLO_VI_WIN_EY(ey));
+}
+
+static int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on)
+{
+ u8 ch = idx * 4;
+
+ if (ch >= solo_dev->nr_chans)
+ return -EINVAL;
+
+ if (!on) {
+ u8 i;
+
+ for (i = ch; i < ch + 4; i++)
+ solo_win_setup(solo_dev, i, solo_dev->video_hsize,
+ solo_vlines(solo_dev),
+ solo_dev->video_hsize,
+ solo_vlines(solo_dev), 0);
+ return 0;
+ }
+
+ /* Row 1 */
+ solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2,
+ solo_vlines(solo_dev) / 2, 3);
+ solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0,
+ solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3);
+ /* Row 2 */
+ solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2,
+ solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3);
+ solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2,
+ solo_vlines(solo_dev) / 2, solo_dev->video_hsize,
+ solo_vlines(solo_dev), 3);
+
+ return 0;
+}
+
+static int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on)
+{
+ int sy, ysize, hsize, i;
+
+ if (!on) {
+ for (i = 0; i < 16; i++)
+ solo_win_setup(solo_dev, i, solo_dev->video_hsize,
+ solo_vlines(solo_dev),
+ solo_dev->video_hsize,
+ solo_vlines(solo_dev), 0);
+ return 0;
+ }
+
+ ysize = solo_vlines(solo_dev) / 4;
+ hsize = solo_dev->video_hsize / 4;
+
+ for (sy = 0, i = 0; i < 4; i++, sy += ysize) {
+ solo_win_setup(solo_dev, i * 4, 0, sy, hsize,
+ sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy,
+ hsize * 2, sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy,
+ hsize * 3, sy + ysize, 5);
+ solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy,
+ solo_dev->video_hsize, sy + ysize, 5);
+ }
+
+ return 0;
+}
+
+static int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on)
+{
+ u8 ext_ch;
+
+ if (ch < solo_dev->nr_chans) {
+ solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize,
+ on ? 0 : solo_vlines(solo_dev),
+ solo_dev->video_hsize, solo_vlines(solo_dev),
+ on ? 1 : 0);
+ return 0;
+ }
+
+ if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
+ return -EINVAL;
+
+ ext_ch = ch - solo_dev->nr_chans;
+
+ /* 4up's first */
+ if (ext_ch < 4)
+ return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on);
+
+ /* Remaining case is 16up for 16-port */
+ return solo_v4l2_ch_ext_16up(solo_dev, on);
+}
+
+static int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch)
+{
+ if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
+ return -EINVAL;
+
+ erase_on(solo_dev);
+
+ solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0);
+ solo_v4l2_ch(solo_dev, ch, 1);
+
+ solo_dev->cur_disp_ch = ch;
+
+ return 0;
+}
+
+static void solo_fillbuf(struct solo_dev *solo_dev,
+ struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ dma_addr_t addr;
+ unsigned int fdma_addr;
+ int error = -1;
+ int i;
+
+ addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (!addr)
+ goto finish_buf;
+
+ if (erase_off(solo_dev)) {
+ void *p = vb2_plane_vaddr(vb, 0);
+ int image_size = solo_image_size(solo_dev);
+
+ for (i = 0; i < image_size; i += 2) {
+ ((u8 *)p)[i] = 0x80;
+ ((u8 *)p)[i + 1] = 0x00;
+ }
+ error = 0;
+ } else {
+ fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write *
+ (SOLO_HW_BPL * solo_vlines(solo_dev)));
+
+ error = solo_p2m_dma_t(solo_dev, 0, addr, fdma_addr,
+ solo_bytesperline(solo_dev),
+ solo_vlines(solo_dev), SOLO_HW_BPL);
+ }
+
+finish_buf:
+ if (!error) {
+ vb2_set_plane_payload(vb, 0,
+ solo_vlines(solo_dev) * solo_bytesperline(solo_dev));
+ vbuf->sequence = solo_dev->sequence++;
+ vb->timestamp = ktime_get_ns();
+ }
+
+ vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+}
+
+static void solo_thread_try(struct solo_dev *solo_dev)
+{
+ struct solo_vb2_buf *vb;
+
+ /* Only "break" from this loop if slock is held, otherwise
+ * just return. */
+ for (;;) {
+ unsigned int cur_write;
+
+ cur_write = SOLO_VI_STATUS0_PAGE(
+ solo_reg_read(solo_dev, SOLO_VI_STATUS0));
+ if (cur_write == solo_dev->old_write)
+ return;
+
+ spin_lock(&solo_dev->slock);
+
+ if (list_empty(&solo_dev->vidq_active))
+ break;
+
+ vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf,
+ list);
+
+ solo_dev->old_write = cur_write;
+ list_del(&vb->list);
+
+ spin_unlock(&solo_dev->slock);
+
+ solo_fillbuf(solo_dev, &vb->vb.vb2_buf);
+ }
+
+ assert_spin_locked(&solo_dev->slock);
+ spin_unlock(&solo_dev->slock);
+}
+
+static int solo_thread(void *data)
+{
+ struct solo_dev *solo_dev = data;
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_freezable();
+ add_wait_queue(&solo_dev->disp_thread_wait, &wait);
+
+ for (;;) {
+ long timeout = schedule_timeout_interruptible(HZ);
+
+ if (timeout == -ERESTARTSYS || kthread_should_stop())
+ break;
+ solo_thread_try(solo_dev);
+ try_to_freeze();
+ }
+
+ remove_wait_queue(&solo_dev->disp_thread_wait, &wait);
+
+ return 0;
+}
+
+static int solo_start_thread(struct solo_dev *solo_dev)
+{
+ int ret = 0;
+
+ solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp");
+
+ if (IS_ERR(solo_dev->kthread)) {
+ ret = PTR_ERR(solo_dev->kthread);
+ solo_dev->kthread = NULL;
+ return ret;
+ }
+ solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN);
+
+ return ret;
+}
+
+static void solo_stop_thread(struct solo_dev *solo_dev)
+{
+ if (!solo_dev->kthread)
+ return;
+
+ solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN);
+ kthread_stop(solo_dev->kthread);
+ solo_dev->kthread = NULL;
+}
+
+static int solo_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ sizes[0] = solo_image_size(solo_dev);
+ *num_planes = 1;
+
+ if (*num_buffers < MIN_VID_BUFFERS)
+ *num_buffers = MIN_VID_BUFFERS;
+
+ return 0;
+}
+
+static int solo_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ solo_dev->sequence = 0;
+ return solo_start_thread(solo_dev);
+}
+
+static void solo_stop_streaming(struct vb2_queue *q)
+{
+ struct solo_dev *solo_dev = vb2_get_drv_priv(q);
+
+ solo_stop_thread(solo_dev);
+
+ spin_lock(&solo_dev->slock);
+ while (!list_empty(&solo_dev->vidq_active)) {
+ struct solo_vb2_buf *buf = list_entry(
+ solo_dev->vidq_active.next,
+ struct solo_vb2_buf, list);
+
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+ spin_unlock(&solo_dev->slock);
+ INIT_LIST_HEAD(&solo_dev->vidq_active);
+}
+
+static void solo_buf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct solo_dev *solo_dev = vb2_get_drv_priv(vq);
+ struct solo_vb2_buf *solo_vb =
+ container_of(vbuf, struct solo_vb2_buf, vb);
+
+ spin_lock(&solo_dev->slock);
+ list_add_tail(&solo_vb->list, &solo_dev->vidq_active);
+ spin_unlock(&solo_dev->slock);
+ wake_up_interruptible(&solo_dev->disp_thread_wait);
+}
+
+static const struct vb2_ops solo_video_qops = {
+ .queue_setup = solo_queue_setup,
+ .buf_queue = solo_buf_queue,
+ .start_streaming = solo_start_streaming,
+ .stop_streaming = solo_stop_streaming,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+};
+
+static int solo_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver));
+ strscpy(cap->card, "Softlogic 6x10", sizeof(cap->card));
+ return 0;
+}
+
+static int solo_enum_ext_input(struct solo_dev *solo_dev,
+ struct v4l2_input *input)
+{
+ int ext = input->index - solo_dev->nr_chans;
+ unsigned int nup, first;
+
+ if (ext >= solo_dev->nr_ext)
+ return -EINVAL;
+
+ nup = (ext == 4) ? 16 : 4;
+ first = (ext & 3) << 2; /* first channel in the n-up */
+ snprintf(input->name, sizeof(input->name),
+ "Multi %d-up (cameras %d-%d)",
+ nup, first + 1, first + nup);
+ /* Possible outputs:
+ * Multi 4-up (cameras 1-4)
+ * Multi 4-up (cameras 5-8)
+ * Multi 4-up (cameras 9-12)
+ * Multi 4-up (cameras 13-16)
+ * Multi 16-up (cameras 1-16)
+ */
+ return 0;
+}
+
+static int solo_enum_input(struct file *file, void *priv,
+ struct v4l2_input *input)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ if (input->index >= solo_dev->nr_chans) {
+ int ret = solo_enum_ext_input(solo_dev, input);
+
+ if (ret < 0)
+ return ret;
+ } else {
+ snprintf(input->name, sizeof(input->name), "Camera %d",
+ input->index + 1);
+
+ /* We can only check this for normal inputs */
+ if (!tw28_get_video_status(solo_dev, input->index))
+ input->status = V4L2_IN_ST_NO_SIGNAL;
+ }
+
+ input->type = V4L2_INPUT_TYPE_CAMERA;
+ input->std = solo_dev->vfd->tvnorms;
+ return 0;
+}
+
+static int solo_set_input(struct file *file, void *priv, unsigned int index)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+ int ret = solo_v4l2_set_ch(solo_dev, index);
+
+ if (!ret) {
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+ }
+
+ return ret;
+}
+
+static int solo_get_input(struct file *file, void *priv, unsigned int *index)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ *index = solo_dev->cur_disp_ch;
+
+ return 0;
+}
+
+static int solo_enum_fmt_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ if (f->index)
+ return -EINVAL;
+
+ f->pixelformat = V4L2_PIX_FMT_UYVY;
+ return 0;
+}
+
+static int solo_try_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ int image_size = solo_image_size(solo_dev);
+
+ if (pix->pixelformat != V4L2_PIX_FMT_UYVY)
+ return -EINVAL;
+
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_vlines(solo_dev);
+ pix->sizeimage = image_size;
+ pix->field = V4L2_FIELD_INTERLACED;
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ return 0;
+}
+
+static int solo_set_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ if (vb2_is_busy(&solo_dev->vidq))
+ return -EBUSY;
+
+ /* For right now, if it doesn't match our running config,
+ * then fail */
+ return solo_try_fmt_cap(file, priv, f);
+}
+
+static int solo_get_fmt_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+
+ pix->width = solo_dev->video_hsize;
+ pix->height = solo_vlines(solo_dev);
+ pix->pixelformat = V4L2_PIX_FMT_UYVY;
+ pix->field = V4L2_FIELD_INTERLACED;
+ pix->sizeimage = solo_image_size(solo_dev);
+ pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+ pix->bytesperline = solo_bytesperline(solo_dev);
+
+ return 0;
+}
+
+static int solo_g_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+ *i = V4L2_STD_NTSC_M;
+ else
+ *i = V4L2_STD_PAL;
+ return 0;
+}
+
+int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz)
+{
+ int i;
+
+ /* Make sure all video nodes are idle */
+ if (vb2_is_busy(&solo_dev->vidq))
+ return -EBUSY;
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq))
+ return -EBUSY;
+ solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL :
+ SOLO_VO_FMT_TYPE_NTSC;
+ /* Reconfigure for the new standard */
+ solo_disp_init(solo_dev);
+ solo_enc_init(solo_dev);
+ solo_tw28_init(solo_dev);
+ for (i = 0; i < solo_dev->nr_chans; i++)
+ solo_update_mode(solo_dev->v4l2_enc[i]);
+ return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch);
+}
+
+static int solo_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+ struct solo_dev *solo_dev = video_drvdata(file);
+
+ return solo_set_video_type(solo_dev, std & V4L2_STD_625_50);
+}
+
+static int solo_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct solo_dev *solo_dev =
+ container_of(ctrl->handler, struct solo_dev, disp_hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MOTION_TRACE:
+ if (ctrl->val) {
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER,
+ SOLO_VI_MOTION_Y_ADD |
+ SOLO_VI_MOTION_Y_VALUE(0x20) |
+ SOLO_VI_MOTION_CB_VALUE(0x10) |
+ SOLO_VI_MOTION_CR_VALUE(0x10));
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR,
+ SOLO_VI_MOTION_CR_ADD |
+ SOLO_VI_MOTION_Y_VALUE(0x10) |
+ SOLO_VI_MOTION_CB_VALUE(0x80) |
+ SOLO_VI_MOTION_CR_VALUE(0x10));
+ } else {
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0);
+ solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0);
+ }
+ return 0;
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static const struct v4l2_file_operations solo_v4l2_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = {
+ .vidioc_querycap = solo_querycap,
+ .vidioc_s_std = solo_s_std,
+ .vidioc_g_std = solo_g_std,
+ /* Input callbacks */
+ .vidioc_enum_input = solo_enum_input,
+ .vidioc_s_input = solo_set_input,
+ .vidioc_g_input = solo_get_input,
+ /* Video capture format callbacks */
+ .vidioc_enum_fmt_vid_cap = solo_enum_fmt_cap,
+ .vidioc_try_fmt_vid_cap = solo_try_fmt_cap,
+ .vidioc_s_fmt_vid_cap = solo_set_fmt_cap,
+ .vidioc_g_fmt_vid_cap = solo_get_fmt_cap,
+ /* Streaming I/O */
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ /* Logging and events */
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device solo_v4l2_template = {
+ .name = SOLO6X10_NAME,
+ .fops = &solo_v4l2_fops,
+ .ioctl_ops = &solo_v4l2_ioctl_ops,
+ .minor = -1,
+ .release = video_device_release,
+ .tvnorms = V4L2_STD_NTSC_M | V4L2_STD_PAL,
+ .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+ V4L2_CAP_STREAMING,
+};
+
+static const struct v4l2_ctrl_ops solo_ctrl_ops = {
+ .s_ctrl = solo_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config solo_motion_trace_ctrl = {
+ .ops = &solo_ctrl_ops,
+ .id = V4L2_CID_MOTION_TRACE,
+ .name = "Motion Detection Trace",
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .max = 1,
+ .step = 1,
+};
+
+int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr)
+{
+ int ret;
+ int i;
+
+ init_waitqueue_head(&solo_dev->disp_thread_wait);
+ spin_lock_init(&solo_dev->slock);
+ mutex_init(&solo_dev->lock);
+ INIT_LIST_HEAD(&solo_dev->vidq_active);
+
+ solo_dev->vfd = video_device_alloc();
+ if (!solo_dev->vfd)
+ return -ENOMEM;
+
+ *solo_dev->vfd = solo_v4l2_template;
+ solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev;
+ solo_dev->vfd->queue = &solo_dev->vidq;
+ solo_dev->vfd->lock = &solo_dev->lock;
+ v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1);
+ v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL);
+ if (solo_dev->disp_hdl.error) {
+ ret = solo_dev->disp_hdl.error;
+ goto fail;
+ }
+ solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl;
+
+ video_set_drvdata(solo_dev->vfd, solo_dev);
+
+ solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+ solo_dev->vidq.ops = &solo_video_qops;
+ solo_dev->vidq.mem_ops = &vb2_dma_contig_memops;
+ solo_dev->vidq.drv_priv = solo_dev;
+ solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ solo_dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM;
+ solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf);
+ solo_dev->vidq.lock = &solo_dev->lock;
+ solo_dev->vidq.dev = &solo_dev->pdev->dev;
+ ret = vb2_queue_init(&solo_dev->vidq);
+ if (ret < 0)
+ goto fail;
+
+ /* Cycle all the channels and clear */
+ for (i = 0; i < solo_dev->nr_chans; i++) {
+ solo_v4l2_set_ch(solo_dev, i);
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+ }
+
+ /* Set the default display channel */
+ solo_v4l2_set_ch(solo_dev, 0);
+ while (erase_off(solo_dev))
+ /* Do nothing */;
+
+ ret = video_register_device(solo_dev->vfd, VFL_TYPE_VIDEO, nr);
+ if (ret < 0)
+ goto fail;
+
+ snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)",
+ SOLO6X10_NAME, solo_dev->vfd->num);
+
+ dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with %d inputs (%d extended)\n",
+ solo_dev->vfd->num,
+ solo_dev->nr_chans, solo_dev->nr_ext);
+
+ return 0;
+
+fail:
+ video_device_release(solo_dev->vfd);
+ v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
+ solo_dev->vfd = NULL;
+ return ret;
+}
+
+void solo_v4l2_exit(struct solo_dev *solo_dev)
+{
+ if (solo_dev->vfd == NULL)
+ return;
+
+ video_unregister_device(solo_dev->vfd);
+ v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
+ solo_dev->vfd = NULL;
+}
diff --git a/drivers/media/pci/solo6x10/solo6x10.h b/drivers/media/pci/solo6x10/solo6x10.h
new file mode 100644
index 000000000..126f6fb7b
--- /dev/null
+++ b/drivers/media/pci/solo6x10/solo6x10.h
@@ -0,0 +1,380 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
+ *
+ * Original author:
+ * Ben Collins <bcollins@ubuntu.com>
+ *
+ * Additional work by:
+ * John Brooks <john.brooks@bluecherry.net>
+ */
+
+#ifndef __SOLO6X10_H
+#define __SOLO6X10_H
+
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/stringify.h>
+#include <linux/io.h>
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <linux/gpio/driver.h>
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "solo6x10-regs.h"
+
+#ifndef PCI_VENDOR_ID_SOFTLOGIC
+#define PCI_VENDOR_ID_SOFTLOGIC 0x9413
+#define PCI_DEVICE_ID_SOLO6010 0x6010
+#define PCI_DEVICE_ID_SOLO6110 0x6110
+#endif
+
+#ifndef PCI_VENDOR_ID_BLUECHERRY
+#define PCI_VENDOR_ID_BLUECHERRY 0x1BB3
+/* Neugent Softlogic 6010 based cards */
+#define PCI_DEVICE_ID_NEUSOLO_4 0x4304
+#define PCI_DEVICE_ID_NEUSOLO_9 0x4309
+#define PCI_DEVICE_ID_NEUSOLO_16 0x4310
+/* Bluecherry Softlogic 6010 based cards */
+#define PCI_DEVICE_ID_BC_SOLO_4 0x4E04
+#define PCI_DEVICE_ID_BC_SOLO_9 0x4E09
+#define PCI_DEVICE_ID_BC_SOLO_16 0x4E10
+/* Bluecherry Softlogic 6110 based cards */
+#define PCI_DEVICE_ID_BC_6110_4 0x5304
+#define PCI_DEVICE_ID_BC_6110_8 0x5308
+#define PCI_DEVICE_ID_BC_6110_16 0x5310
+#endif /* Bluecherry */
+
+/* Used in pci_device_id, and solo_dev->type */
+#define SOLO_DEV_6010 0
+#define SOLO_DEV_6110 1
+
+#define SOLO6X10_NAME "solo6x10"
+
+#define SOLO_MAX_CHANNELS 16
+
+#define SOLO6X10_VERSION "3.0.0"
+
+/*
+ * The SOLO6x10 actually has 8 i2c channels, but we only use 2.
+ * 0 - Techwell chip(s)
+ * 1 - SAA7128
+ */
+#define SOLO_I2C_ADAPTERS 2
+#define SOLO_I2C_TW 0
+#define SOLO_I2C_SAA 1
+
+/* DMA Engine setup */
+#define SOLO_NR_P2M 4
+#define SOLO_NR_P2M_DESC 256
+#define SOLO_P2M_DESC_SIZE (SOLO_NR_P2M_DESC * 16)
+
+/* Encoder standard modes */
+#define SOLO_ENC_MODE_CIF 2
+#define SOLO_ENC_MODE_HD1 1
+#define SOLO_ENC_MODE_D1 9
+
+#define SOLO_DEFAULT_QP 3
+
+#define SOLO_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000)
+#define V4L2_CID_MOTION_TRACE (SOLO_CID_CUSTOM_BASE+2)
+#define V4L2_CID_OSD_TEXT (SOLO_CID_CUSTOM_BASE+3)
+
+/*
+ * Motion thresholds are in a table of 64x64 samples, with
+ * each sample representing 16x16 pixels of the source. In
+ * effect, 44x30 samples are used for NTSC, and 44x36 for PAL.
+ * The 5th sample on the 10th row is (10*64)+5 = 645.
+ *
+ * Internally it is stored as a 45x45 array (45*16 = 720, which is the
+ * maximum PAL/NTSC width).
+ */
+#define SOLO_MOTION_SZ (45)
+
+enum SOLO_I2C_STATE {
+ IIC_STATE_IDLE,
+ IIC_STATE_START,
+ IIC_STATE_READ,
+ IIC_STATE_WRITE,
+ IIC_STATE_STOP
+};
+
+/* Defined in Table 4-16, Page 68-69 of the 6010 Datasheet */
+struct solo_p2m_desc {
+ u32 ctrl;
+ u32 cfg;
+ u32 dma_addr;
+ u32 ext_addr;
+};
+
+struct solo_p2m_dev {
+ struct mutex mutex;
+ struct completion completion;
+ int desc_count;
+ int desc_idx;
+ struct solo_p2m_desc *descs;
+ int error;
+};
+
+#define OSD_TEXT_MAX 44
+
+struct solo_vb2_buf {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+enum solo_enc_types {
+ SOLO_ENC_TYPE_STD,
+ SOLO_ENC_TYPE_EXT,
+};
+
+struct solo_enc_dev {
+ struct solo_dev *solo_dev;
+ /* V4L2 Items */
+ struct v4l2_ctrl_handler hdl;
+ struct v4l2_ctrl *md_thresholds;
+ struct video_device *vfd;
+ /* General accounting */
+ struct mutex lock;
+ spinlock_t motion_lock;
+ u8 ch;
+ u8 mode, gop, qp, interlaced, interval;
+ u8 bw_weight;
+ u16 motion_thresh;
+ bool motion_global;
+ bool motion_enabled;
+ u16 width;
+ u16 height;
+
+ /* OSD buffers */
+ char osd_text[OSD_TEXT_MAX + 1];
+ u8 osd_buf[SOLO_EOSD_EXT_SIZE_MAX]
+ __aligned(4);
+
+ /* VOP stuff */
+ u8 vop[64];
+ int vop_len;
+ u8 jpeg_header[1024];
+ int jpeg_len;
+
+ u32 fmt;
+ enum solo_enc_types type;
+ u32 sequence;
+ struct vb2_queue vidq;
+ struct list_head vidq_active;
+ int desc_count;
+ int desc_nelts;
+ struct solo_p2m_desc *desc_items;
+ dma_addr_t desc_dma;
+ spinlock_t av_lock;
+};
+
+/* The SOLO6x10 PCI Device */
+struct solo_dev {
+ /* General stuff */
+ struct pci_dev *pdev;
+ int type;
+ unsigned int time_sync;
+ unsigned int usec_lsb;
+ unsigned int clock_mhz;
+ u8 __iomem *reg_base;
+ int nr_chans;
+ int nr_ext;
+ u32 irq_mask;
+ u32 motion_mask;
+ struct v4l2_device v4l2_dev;
+#ifdef CONFIG_GPIOLIB
+ /* GPIO */
+ struct gpio_chip gpio_dev;
+#endif
+
+ /* tw28xx accounting */
+ u8 tw2865, tw2864, tw2815;
+ u8 tw28_cnt;
+
+ /* i2c related items */
+ struct i2c_adapter i2c_adap[SOLO_I2C_ADAPTERS];
+ enum SOLO_I2C_STATE i2c_state;
+ struct mutex i2c_mutex;
+ int i2c_id;
+ wait_queue_head_t i2c_wait;
+ struct i2c_msg *i2c_msg;
+ unsigned int i2c_msg_num;
+ unsigned int i2c_msg_ptr;
+
+ /* P2M DMA Engine */
+ struct solo_p2m_dev p2m_dev[SOLO_NR_P2M];
+ atomic_t p2m_count;
+ int p2m_jiffies;
+ unsigned int p2m_timeouts;
+
+ /* V4L2 Display items */
+ struct video_device *vfd;
+ unsigned int erasing;
+ unsigned int frame_blank;
+ u8 cur_disp_ch;
+ wait_queue_head_t disp_thread_wait;
+ struct v4l2_ctrl_handler disp_hdl;
+
+ /* V4L2 Encoder items */
+ struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS];
+ u16 enc_bw_remain;
+ /* IDX into hw mp4 encoder */
+ u8 enc_idx;
+
+ /* Current video settings */
+ u32 video_type;
+ u16 video_hsize, video_vsize;
+ u16 vout_hstart, vout_vstart;
+ u16 vin_hstart, vin_vstart;
+ u8 fps;
+
+ /* JPEG Qp setting */
+ spinlock_t jpeg_qp_lock;
+ u32 jpeg_qp[2];
+
+ /* Audio components */
+ struct snd_card *snd_card;
+ struct snd_pcm *snd_pcm;
+ atomic_t snd_users;
+ int g723_hw_idx;
+
+ /* sysfs stuffs */
+ struct device dev;
+ int sdram_size;
+ struct bin_attribute sdram_attr;
+ unsigned int sys_config;
+
+ /* Ring thread */
+ struct task_struct *ring_thread;
+ wait_queue_head_t ring_thread_wait;
+
+ /* VOP_HEADER handling */
+ void *vh_buf;
+ dma_addr_t vh_dma;
+ int vh_size;
+
+ /* Buffer handling */
+ struct vb2_queue vidq;
+ u32 sequence;
+ struct task_struct *kthread;
+ struct mutex lock;
+ spinlock_t slock;
+ int old_write;
+ struct list_head vidq_active;
+};
+
+static inline u32 solo_reg_read(struct solo_dev *solo_dev, int reg)
+{
+ return readl(solo_dev->reg_base + reg);
+}
+
+static inline void solo_reg_write(struct solo_dev *solo_dev, int reg,
+ u32 data)
+{
+ u16 val;
+
+ writel(data, solo_dev->reg_base + reg);
+ pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val);
+}
+
+static inline void solo_irq_on(struct solo_dev *dev, u32 mask)
+{
+ dev->irq_mask |= mask;
+ solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask);
+}
+
+static inline void solo_irq_off(struct solo_dev *dev, u32 mask)
+{
+ dev->irq_mask &= ~mask;
+ solo_reg_write(dev, SOLO_IRQ_MASK, dev->irq_mask);
+}
+
+/* Init/exit routines for subsystems */
+int solo_disp_init(struct solo_dev *solo_dev);
+void solo_disp_exit(struct solo_dev *solo_dev);
+
+int solo_gpio_init(struct solo_dev *solo_dev);
+void solo_gpio_exit(struct solo_dev *solo_dev);
+
+int solo_i2c_init(struct solo_dev *solo_dev);
+void solo_i2c_exit(struct solo_dev *solo_dev);
+
+int solo_p2m_init(struct solo_dev *solo_dev);
+void solo_p2m_exit(struct solo_dev *solo_dev);
+
+int solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr);
+void solo_v4l2_exit(struct solo_dev *solo_dev);
+
+int solo_enc_init(struct solo_dev *solo_dev);
+void solo_enc_exit(struct solo_dev *solo_dev);
+
+int solo_enc_v4l2_init(struct solo_dev *solo_dev, unsigned nr);
+void solo_enc_v4l2_exit(struct solo_dev *solo_dev);
+
+int solo_g723_init(struct solo_dev *solo_dev);
+void solo_g723_exit(struct solo_dev *solo_dev);
+
+/* ISR's */
+int solo_i2c_isr(struct solo_dev *solo_dev);
+void solo_p2m_isr(struct solo_dev *solo_dev, int id);
+void solo_p2m_error_isr(struct solo_dev *solo_dev);
+void solo_enc_v4l2_isr(struct solo_dev *solo_dev);
+void solo_g723_isr(struct solo_dev *solo_dev);
+void solo_motion_isr(struct solo_dev *solo_dev);
+void solo_video_in_isr(struct solo_dev *solo_dev);
+
+/* i2c read/write */
+u8 solo_i2c_readbyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off);
+void solo_i2c_writebyte(struct solo_dev *solo_dev, int id, u8 addr, u8 off,
+ u8 data);
+
+/* P2M DMA */
+int solo_p2m_dma_t(struct solo_dev *solo_dev, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+int solo_p2m_dma(struct solo_dev *solo_dev, int wr,
+ void *sys_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+void solo_p2m_fill_desc(struct solo_p2m_desc *desc, int wr,
+ dma_addr_t dma_addr, u32 ext_addr, u32 size,
+ int repeat, u32 ext_size);
+int solo_p2m_dma_desc(struct solo_dev *solo_dev,
+ struct solo_p2m_desc *desc, dma_addr_t desc_dma,
+ int desc_cnt);
+
+/* Global s_std ioctl */
+int solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz);
+void solo_update_mode(struct solo_enc_dev *solo_enc);
+
+/* Set the threshold for motion detection */
+int solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val);
+int solo_set_motion_block(struct solo_dev *solo_dev, u8 ch,
+ const u16 *thresholds);
+#define SOLO_DEF_MOT_THRESH 0x0300
+
+/* Write text on OSD */
+int solo_osd_print(struct solo_enc_dev *solo_enc);
+
+/* EEPROM commands */
+unsigned int solo_eeprom_ewen(struct solo_dev *solo_dev, int w_en);
+__be16 solo_eeprom_read(struct solo_dev *solo_dev, int loc);
+int solo_eeprom_write(struct solo_dev *solo_dev, int loc,
+ __be16 data);
+
+/* JPEG Qp functions */
+void solo_s_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch,
+ unsigned int qp);
+int solo_g_jpeg_qp(struct solo_dev *solo_dev, unsigned int ch);
+
+#define CHK_FLAGS(v, flags) (((v) & (flags)) == (flags))
+
+#endif /* __SOLO6X10_H */