[PATCH 181/524] Staging: solo6x10: New driver (staging) for Softlogic 6x10

Greg Kroah-Hartman gregkh at suse.de
Thu Aug 5 22:19:24 UTC 2010


From: Ben Collins <bcollins at bluecherry.net>

This driver supports Softlogic 6x10 based codec cards

Signed-off-by: Ben Collins <bcollins at bluecherry.net>
Signed-off-by: Greg Kroah-Hartman <gregkh at suse.de>
---
 drivers/staging/solo6x10/Kconfig              |    7 +
 drivers/staging/solo6x10/Makefile             |    6 +
 drivers/staging/solo6x10/solo6010-core.c      |  282 +++++
 drivers/staging/solo6x10/solo6010-disp.c      |  271 +++++
 drivers/staging/solo6x10/solo6010-enc.c       |  229 ++++
 drivers/staging/solo6x10/solo6010-g723.c      |  398 +++++++
 drivers/staging/solo6x10/solo6010-gpio.c      |  103 ++
 drivers/staging/solo6x10/solo6010-i2c.c       |  331 ++++++
 drivers/staging/solo6x10/solo6010-jpeg.h      |  105 ++
 drivers/staging/solo6x10/solo6010-offsets.h   |   78 ++
 drivers/staging/solo6x10/solo6010-osd-font.h  |  154 +++
 drivers/staging/solo6x10/solo6010-p2m.c       |  208 ++++
 drivers/staging/solo6x10/solo6010-registers.h |  657 +++++++++++
 drivers/staging/solo6x10/solo6010-tw28.c      |  678 +++++++++++
 drivers/staging/solo6x10/solo6010-tw28.h      |   65 +
 drivers/staging/solo6x10/solo6010-v4l2-enc.c  | 1564 +++++++++++++++++++++++++
 drivers/staging/solo6x10/solo6010-v4l2.c      |  859 ++++++++++++++
 drivers/staging/solo6x10/solo6010.h           |  316 +++++
 18 files changed, 6311 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/solo6x10/Kconfig
 create mode 100644 drivers/staging/solo6x10/Makefile
 create mode 100644 drivers/staging/solo6x10/solo6010-core.c
 create mode 100644 drivers/staging/solo6x10/solo6010-disp.c
 create mode 100644 drivers/staging/solo6x10/solo6010-enc.c
 create mode 100644 drivers/staging/solo6x10/solo6010-g723.c
 create mode 100644 drivers/staging/solo6x10/solo6010-gpio.c
 create mode 100644 drivers/staging/solo6x10/solo6010-i2c.c
 create mode 100644 drivers/staging/solo6x10/solo6010-jpeg.h
 create mode 100644 drivers/staging/solo6x10/solo6010-offsets.h
 create mode 100644 drivers/staging/solo6x10/solo6010-osd-font.h
 create mode 100644 drivers/staging/solo6x10/solo6010-p2m.c
 create mode 100644 drivers/staging/solo6x10/solo6010-registers.h
 create mode 100644 drivers/staging/solo6x10/solo6010-tw28.c
 create mode 100644 drivers/staging/solo6x10/solo6010-tw28.h
 create mode 100644 drivers/staging/solo6x10/solo6010-v4l2-enc.c
 create mode 100644 drivers/staging/solo6x10/solo6010-v4l2.c
 create mode 100644 drivers/staging/solo6x10/solo6010.h

diff --git a/drivers/staging/solo6x10/Kconfig b/drivers/staging/solo6x10/Kconfig
new file mode 100644
index 0000000..8aaa363
--- /dev/null
+++ b/drivers/staging/solo6x10/Kconfig
@@ -0,0 +1,7 @@
+config SOLO6X10
+	tristate "Softlogic 6x10 MPEG codec cards"
+	depends on PCI && VIDEO_DEV
+	select VIDEOBUF_DMA_CONTIG
+	---help---
+	  This driver supports the Softlogic based MPEG-4 and h.264 codec
+	  codec cards.
diff --git a/drivers/staging/solo6x10/Makefile b/drivers/staging/solo6x10/Makefile
new file mode 100644
index 0000000..7e70044
--- /dev/null
+++ b/drivers/staging/solo6x10/Makefile
@@ -0,0 +1,6 @@
+solo6x10-objs	:= solo6010-core.o solo6010-i2c.o solo6010-p2m.o \
+		   solo6010-v4l2.o solo6010-tw28.o solo6010-gpio.o \
+		   solo6010-disp.o solo6010-enc.o solo6010-v4l2-enc.o \
+		   solo6010-g723.o
+
+obj-$(CONFIG_SOLO6X10) := solo6x10.o
diff --git a/drivers/staging/solo6x10/solo6010-core.c b/drivers/staging/solo6x10/solo6010-core.c
new file mode 100644
index 0000000..98c6739
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-core.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/videodev2.h>
+
+#include "solo6010.h"
+#include "solo6010-tw28.h"
+
+MODULE_DESCRIPTION("Softlogic 6010 MP4 Encoder/Decoder V4L2/ALSA Driver");
+MODULE_AUTHOR("Ben Collins <bcollins at bluecherry.net>");
+MODULE_VERSION(SOLO6010_VERSION);
+MODULE_LICENSE("GPL");
+
+void solo6010_irq_on(struct solo6010_dev *solo_dev, u32 mask)
+{
+	solo_dev->irq_mask |= mask;
+	solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask);
+}
+
+void solo6010_irq_off(struct solo6010_dev *solo_dev, u32 mask)
+{
+	solo_dev->irq_mask &= ~mask;
+	solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask);
+}
+
+/* XXX We should check the return value of the sub-device ISR's */
+static irqreturn_t solo6010_isr(int irq, void *data)
+{
+	struct solo6010_dev *solo_dev = data;
+	u32 status;
+	int i;
+
+	status = solo_reg_read(solo_dev, SOLO_IRQ_STAT);
+	if (!status)
+		return IRQ_NONE;
+
+	if (status & ~solo_dev->irq_mask) {
+		solo_reg_write(solo_dev, SOLO_IRQ_STAT,
+			       status & ~solo_dev->irq_mask);
+		status &= solo_dev->irq_mask;
+	}
+
+	if (status & SOLO_IRQ_PCI_ERR) {
+		u32 err = solo_reg_read(solo_dev, SOLO_PCI_ERR);
+		solo_p2m_error_isr(solo_dev, err);
+		solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_PCI_ERR);
+	}
+
+	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);
+
+	/* Call this first so enc gets detected flag set */
+	if (status & SOLO_IRQ_MOTION)
+		solo_motion_isr(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 solo6010_dev *solo_dev)
+{
+	struct pci_dev *pdev;
+
+	if (!solo_dev)
+		return;
+
+	pdev = solo_dev->pdev;
+
+	/* If we never initialized the PCI device, then nothing else
+	 * below here needs cleanup */
+	if (!pdev) {
+		kfree(solo_dev);
+		return;
+	}
+
+	/* 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 */
+	if (solo_dev->reg_base) {
+		solo6010_irq_off(solo_dev, ~0);
+		pci_iounmap(pdev, solo_dev->reg_base);
+		free_irq(pdev->irq, solo_dev);
+	}
+
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	pci_set_drvdata(pdev, NULL);
+
+	kfree(solo_dev);
+}
+
+static int __devinit solo6010_pci_probe(struct pci_dev *pdev,
+					const struct pci_device_id *id)
+{
+	struct solo6010_dev *solo_dev;
+	int ret;
+	int sdram;
+	u8 chip_id;
+
+	if ((solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	solo_dev->pdev = pdev;
+	spin_lock_init(&solo_dev->reg_io_lock);
+	pci_set_drvdata(pdev, solo_dev);
+
+	if ((ret = pci_enable_device(pdev)))
+		goto fail_probe;
+
+	pci_set_master(pdev);
+
+	if ((ret = pci_request_regions(pdev, SOLO6010_NAME)))
+		goto fail_probe;
+
+	if ((solo_dev->reg_base = pci_ioremap_bar(pdev, 0)) == 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, "
+				 "defaulting to 4 channels\n",
+				 chip_id);
+		case 5:
+			solo_dev->nr_chans = 4;
+			solo_dev->nr_ext = 1;
+	}
+
+	/* Disable all interrupts to start */
+	solo6010_irq_off(solo_dev, ~0);
+
+	/* Initial global settings */
+	solo_reg_write(solo_dev, SOLO_SYS_CFG, SOLO_SYS_CFG_SDRAM64BIT |
+		       SOLO_SYS_CFG_INPUTDIV(25) |
+		       SOLO_SYS_CFG_FEEDBACKDIV((SOLO_CLOCK_MHZ * 2) - 2) |
+		       SOLO_SYS_CFG_OUTDIV(3));
+	solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, SOLO_CLOCK_MHZ - 1);
+
+	/* PLL locking time of 1ms */
+	mdelay(1);
+
+	ret = request_irq(pdev->irq, solo6010_isr, IRQF_SHARED, SOLO6010_NAME,
+			  solo_dev);
+	if (ret)
+		goto fail_probe;
+
+	/* Handle this from the start */
+	solo6010_irq_on(solo_dev, SOLO_IRQ_PCI_ERR);
+
+	if ((ret = solo_i2c_init(solo_dev)))
+		goto fail_probe;
+
+	/* Setup the DMA engine */
+	sdram = (solo_dev->nr_chans >= 8) ? 2 : 1;
+	solo_reg_write(solo_dev, SOLO_DMA_CTRL,
+		       SOLO_DMA_CTRL_REFRESH_CYCLE(1) |
+		       SOLO_DMA_CTRL_SDRAM_SIZE(sdram) |
+		       SOLO_DMA_CTRL_SDRAM_CLK_INVERT |
+		       SOLO_DMA_CTRL_READ_CLK_SELECT |
+		       SOLO_DMA_CTRL_LATENCY(1));
+
+	if ((ret = solo_p2m_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_disp_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_gpio_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_tw28_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_v4l2_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_enc_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_enc_v4l2_init(solo_dev)))
+		goto fail_probe;
+
+	if ((ret = solo_g723_init(solo_dev)))
+		goto fail_probe;
+
+	return 0;
+
+fail_probe:
+	free_solo_dev(solo_dev);
+	return ret;
+}
+
+static void __devexit solo6010_pci_remove(struct pci_dev *pdev)
+{
+	struct solo6010_dev *solo_dev = pci_get_drvdata(pdev);
+
+	free_solo_dev(solo_dev);
+}
+
+static struct pci_device_id solo6010_id_table[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_4)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_9)},
+	{PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_COMMSOLO_16)},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, solo6010_id_table);
+
+static struct pci_driver solo6010_pci_driver = {
+	.name = SOLO6010_NAME,
+	.id_table = solo6010_id_table,
+	.probe = solo6010_pci_probe,
+	.remove = solo6010_pci_remove,
+};
+
+static int __init solo6010_module_init(void)
+{
+	return pci_register_driver(&solo6010_pci_driver);
+}
+
+static void __exit solo6010_module_exit(void)
+{
+	pci_unregister_driver(&solo6010_pci_driver);
+}
+
+module_init(solo6010_module_init);
+module_exit(solo6010_module_exit);
diff --git a/drivers/staging/solo6x10/solo6010-disp.c b/drivers/staging/solo6x10/solo6010-disp.c
new file mode 100644
index 0000000..555f024
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-disp.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+
+#include "solo6010.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		512
+#define SOLO_MOT_FLAG_AREA		(SOLO_MOT_FLAG_SIZE * 32)
+
+static unsigned video_type;
+module_param(video_type, uint, 0644);
+MODULE_PARM_DESC(video_type, "video_type (0 = NTSC/Default, 1 = PAL)");
+
+static void solo_vin_config(struct solo6010_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));
+
+	solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0);
+	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_disp_config(struct solo6010_dev *solo_dev)
+{
+	solo_dev->vout_hstart = 6;
+	solo_dev->vout_vstart = 8;
+
+	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_FMT_ENC,
+		       solo_dev->video_type |
+		       SOLO_VO_USER_COLOR_SET_NAV |
+		       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));
+
+	solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 5);
+
+	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_dev)));
+
+	solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
+
+	/* Enable channels we support */
+	solo_reg_write(solo_dev, SOLO_VI_CH_ENA, (1 << solo_dev->nr_chans) - 1);
+
+	/* Disable the watchdog */
+	solo_reg_write(solo_dev, SOLO_WATCHDOG, 0);
+}
+
+static int solo_dma_vin_region(struct solo6010_dev *solo_dev, u32 off,
+			       u16 val, int reg_size)
+{
+	u16 buf[64];
+	int i;
+	int ret = 0;
+
+	for (i = 0; i < sizeof(buf) >> 1; i++)
+		buf[i] = val;
+
+	for (i = 0; i < reg_size; i += sizeof(buf))
+		ret |= solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_VIN, 1, buf,
+				    SOLO_MOTION_EXT_ADDR(solo_dev) + off + i,
+				    sizeof(buf));
+
+	return ret;
+}
+
+void solo_set_motion_threshold(struct solo6010_dev *solo_dev, u8 ch, u16 val)
+{
+	if (ch > solo_dev->nr_chans)
+		return;
+
+	solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA +
+			    (ch * SOLO_MOT_THRESH_SIZE * 2),
+			    val, SOLO_MOT_THRESH_REAL);
+}
+
+/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k
+ * threshold and working table for each channel. Atleast that's what the
+ * spec says. However, this code (take from rdk) has some mystery 8k
+ * block right after the flag area, before the first thresh table. */
+static void solo_motion_config(struct solo6010_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 +
+				    SOLO_MOT_THRESH_SIZE +
+				    (i * SOLO_MOT_THRESH_SIZE * 2),
+				    0x0000, SOLO_MOT_THRESH_REAL);
+
+		/* 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 solo6010_dev *solo_dev)
+{
+	int i;
+
+	solo_dev->video_hsize = 704;
+	if (video_type == 0) {
+		solo_dev->video_type = SOLO_VO_FMT_TYPE_NTSC;
+		solo_dev->video_vsize = 240;
+		solo_dev->fps = 30;
+	} else {
+		solo_dev->video_type = SOLO_VO_FMT_TYPE_PAL;
+		solo_dev->video_vsize = 288;
+		solo_dev->fps = 25;
+	}
+
+	solo_vin_config(solo_dev);
+	solo_motion_config(solo_dev);
+	solo_disp_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 solo6010_dev *solo_dev)
+{
+	int i;
+
+	solo6010_irq_off(solo_dev, SOLO_IRQ_MOTION);
+
+	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/staging/solo6x10/solo6010-enc.c b/drivers/staging/solo6x10/solo6010-enc.c
new file mode 100644
index 0000000..a6cf0a8
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-enc.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+
+#include "solo6010.h"
+#include "solo6010-osd-font.h"
+
+#define CAPTURE_MAX_BANDWIDTH		32	// D1 4channel (D1 == 4)
+#define OSG_BUFFER_SIZE			1024
+
+#define VI_PROG_HSIZE			(1280 - 16)
+#define VI_PROG_VSIZE			(1024 - 16)
+
+static void solo_capture_config(struct solo6010_dev *solo_dev)
+{
+	int i, j;
+	unsigned long height;
+	unsigned long width;
+	unsigned char *buf;
+
+	solo_reg_write(solo_dev, SOLO_CAP_BASE,
+		       SOLO_CAP_MAX_PAGE(SOLO_CAP_EXT_MAX_PAGE *
+					 solo_dev->nr_chans) |
+		       SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16));
+	solo_reg_write(solo_dev, SOLO_CAP_BTW,
+		       (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) |
+		       SOLO_CAP_MAX_BANDWIDTH(CAPTURE_MAX_BANDWIDTH));
+
+	/* 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(solo_dev) >> 16);
+	solo_reg_write(solo_dev, SOLO_VE_OSD_CLR,
+		       0xF0 << 16 | 0x80 << 8 | 0x80);
+	solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, 0);
+
+	/* Clear OSG buffer */
+	buf = kzalloc(OSG_BUFFER_SIZE, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	for (i = 0; i < solo_dev->nr_chans; i++) {
+		for (j = 0; j < SOLO_EOSD_EXT_SIZE; j += OSG_BUFFER_SIZE) {
+			solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_MP4E, 1, buf,
+				     SOLO_EOSD_EXT_ADDR(solo_dev) +
+				     (i * SOLO_EOSD_EXT_SIZE) + j,
+				     OSG_BUFFER_SIZE);
+		}
+	}
+	kfree(buf);
+}
+
+int solo_osd_print(struct solo_enc_dev *solo_enc)
+{
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	char *str = solo_enc->osd_text;
+	u8 *buf;
+	u32 reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH);
+	int len = strlen(str);
+	int i, j;
+	int x = 1, y = 1;
+
+	if (len == 0) {
+		reg &= ~(1 << solo_enc->ch);
+		solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg);
+		return 0;
+	}
+
+	buf = kzalloc(SOLO_EOSD_EXT_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	for (i = 0; i < len; i++) {
+		for (j = 0; j < 16; j++) {
+			buf[(j*2) + (i%2) + ((x + (i/2)) * 32) + (y * 2048)] =
+				(solo_osd_font[(str[i] * 4) + (j / 4)]
+					>> ((3 - (j % 4)) * 8)) & 0xff;
+		}
+	}
+
+	solo_p2m_dma(solo_dev, 0, 1, buf, SOLO_EOSD_EXT_ADDR(solo_dev) +
+		     (solo_enc->ch * SOLO_EOSD_EXT_SIZE), SOLO_EOSD_EXT_SIZE);
+        reg |= (1 << solo_enc->ch);
+        solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg);
+
+	kfree(buf);
+
+	return 0;
+}
+
+static void solo_jpeg_config(struct solo6010_dev *solo_dev)
+{
+	solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL,
+		       (2 << 24) | (2 << 16) | (2 << 8) | (2 << 0));
+	solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, 0);
+	solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, 0);
+	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);
+}
+
+static void solo_mp4e_config(struct solo6010_dev *solo_dev)
+{
+	int i;
+
+	/* We can only use VE_INTR_CTRL(0) if we want to support mjpeg */
+	solo_reg_write(solo_dev, SOLO_VE_CFG0,
+		       SOLO_VE_INTR_CTRL(0) |
+		       SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) |
+		       SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16));
+
+	solo_reg_write(solo_dev, SOLO_VE_CFG1,
+		       SOLO_VE_BYTE_ALIGN(2) |
+		       SOLO_VE_INSERT_INDEX | SOLO_VE_MOTION_MODE(0));
+
+	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);
+	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(36 / 4));
+
+	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);
+}
+
+int solo_enc_init(struct solo6010_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);
+	}
+
+	solo6010_irq_on(solo_dev, SOLO_IRQ_ENCODER);
+
+	return 0;
+}
+
+void solo_enc_exit(struct solo6010_dev *solo_dev)
+{
+	int i;
+
+	solo6010_irq_off(solo_dev, SOLO_IRQ_ENCODER);
+
+	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/staging/solo6x10/solo6010-g723.c b/drivers/staging/solo6x10/solo6010-g723.c
new file mode 100644
index 0000000..e82846c
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-g723.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mempool.h>
+#include <linux/poll.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+
+#include "solo6010.h"
+#include "solo6010-tw28.h"
+
+#define G723_INTR_ORDER		0
+#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 MAX_BUFFER		(G723_PERIOD_BYTES * PERIODS_MAX)
+#define IRQ_PAGES		4 // 0 - 4
+#define PERIODS_MIN		(1 << IRQ_PAGES)
+#define PERIODS_MAX		G723_FDMA_PAGES
+
+struct solo_snd_pcm {
+	int				on;
+	spinlock_t			lock;
+	struct solo6010_dev		*solo_dev;
+	unsigned char			g723_buf[G723_PERIOD_BYTES];
+};
+
+static void solo_g723_config(struct solo6010_dev *solo_dev)
+{
+	int clk_div;
+
+	clk_div = SOLO_CLOCK_MHZ / (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(IRQ_PAGES) |
+		      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 solo6010_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;
+
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_G723);
+
+	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 int snd_solo_hw_params(struct snd_pcm_substream *ss,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params));
+}
+
+static int snd_solo_hw_free(struct snd_pcm_substream *ss)
+{
+	return snd_pcm_lib_free_pages(ss);
+}
+
+static 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		= 8000,
+	.rate_max		= 8000,
+	.channels_min		= 1,
+	.channels_max		= 1,
+	.buffer_bytes_max	= MAX_BUFFER,
+	.period_bytes_min	= G723_PERIOD_BYTES,
+	.period_bytes_max	= G723_PERIOD_BYTES,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= PERIODS_MAX,
+};
+
+static int snd_solo_pcm_open(struct snd_pcm_substream *ss)
+{
+	struct solo6010_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)
+		return -ENOMEM;
+
+	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;
+}
+
+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;
+	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 solo6010_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)
+				solo6010_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)
+				solo6010_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 solo6010_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(struct snd_pcm_substream *ss, int channel,
+			     snd_pcm_uframes_t pos, void __user *dst,
+			     snd_pcm_uframes_t count)
+{
+	struct solo_snd_pcm *solo_pcm = snd_pcm_substream_chip(ss);
+	struct solo6010_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(solo_dev, SOLO_P2M_DMA_ID_G723E, 0,
+				   solo_pcm->g723_buf,
+				   SOLO_G723_EXT_ADDR(solo_dev) +
+				   (page * G723_PERIOD_BLOCK) +
+				   (ss->number * G723_PERIOD_BYTES),
+				   G723_PERIOD_BYTES);
+		if (err)
+			return err;
+
+		err = copy_to_user(dst + (i * G723_PERIOD_BYTES),
+				   solo_pcm->g723_buf, G723_PERIOD_BYTES);
+
+		if (err)
+			return err; 
+	}
+
+	return 0;
+}
+
+static struct snd_pcm_ops snd_solo_pcm_ops = {
+	.open = snd_solo_pcm_open,
+	.close = snd_solo_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_solo_hw_params,
+	.hw_free = snd_solo_hw_free,
+	.prepare = snd_solo_pcm_prepare,
+	.trigger = snd_solo_pcm_trigger,
+	.pointer = snd_solo_pcm_pointer,
+	.copy = snd_solo_pcm_copy,
+};
+
+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 solo6010_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 solo6010_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 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 solo6010_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;
+	strcpy(pcm->name, card->shortname);
+
+	for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	     ss; ss = ss->next, i++)
+		sprintf(ss->name, "Camera #%d Audio", i);
+
+	ret = snd_pcm_lib_preallocate_pages_for_all(pcm,
+					SNDRV_DMA_TYPE_CONTINUOUS,
+					snd_dma_continuous_data(GFP_KERNEL),
+					MAX_BUFFER, MAX_BUFFER);
+	if (ret < 0)
+		return ret;
+
+	solo_dev->snd_pcm = pcm;
+
+	return 0;
+}
+
+int solo_g723_init(struct solo6010_dev *solo_dev)
+{
+	static struct snd_device_ops ops = { NULL };
+	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_create(SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0,
+			      &solo_dev->snd_card);
+	if (ret < 0)
+		return ret;
+
+	card = solo_dev->snd_card;
+
+	strcpy(card->driver, SOLO6010_NAME);
+	strcpy(card->shortname, "SOLO-6010 Audio");
+	sprintf(card->longname, "%s on %s IRQ %d", card->shortname,
+		pci_name(solo_dev->pdev), solo_dev->pdev->irq);
+	snd_card_set_dev(card, &solo_dev->pdev->dev);
+
+	ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops);
+	if (ret < 0)
+		goto snd_error;
+
+	/* Mixer controls */
+	strcpy(card->mixername, "SOLO-6010");
+	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)
+		return ret;
+
+	if ((ret = solo_snd_pcm_init(solo_dev)) < 0)
+		goto snd_error;
+
+	if ((ret = snd_card_register(card)) < 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 solo6010_dev *solo_dev)
+{
+	solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0);
+	solo6010_irq_off(solo_dev, SOLO_IRQ_G723);
+
+	snd_card_free(solo_dev->snd_card);
+}
diff --git a/drivers/staging/solo6x10/solo6010-gpio.c b/drivers/staging/solo6x10/solo6010-gpio.c
new file mode 100644
index 0000000..46f7a71
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-gpio.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+#include "solo6010.h"
+
+static void solo_gpio_mode(struct solo6010_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 (!((1 << (port + 16)) & port_mask))
+			continue;
+
+		if (!mode)
+			ret &= ~(1 << port);
+		else
+			ret |= 1 << port;
+	}
+
+	solo_reg_write(solo_dev, SOLO_GPIO_CONFIG_1, ret);
+}
+
+static void solo_gpio_set(struct solo6010_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 solo6010_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 solo6010_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);
+}
+
+int solo_gpio_init(struct solo6010_dev *solo_dev)
+{
+        solo_gpio_config(solo_dev);
+        return 0;
+}
+
+void solo_gpio_exit(struct solo6010_dev *solo_dev)
+{
+	solo_gpio_clear(solo_dev, 0x30);
+	solo_gpio_config(solo_dev);
+}
diff --git a/drivers/staging/solo6x10/solo6010-i2c.c b/drivers/staging/solo6x10/solo6010-i2c.c
new file mode 100644
index 0000000..2bb86fa
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-i2c.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* XXX: The SOLO6010 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 "solo6010.h"
+
+u8 solo_i2c_readbyte(struct solo6010_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 solo6010_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 solo6010_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 solo6010_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 solo6010_dev *solo_dev)
+{
+	solo6010_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 solo6010_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 solo6010_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 solo6010_dev *solo_dev)
+{
+	u32 status = solo_reg_read(solo_dev, SOLO_IIC_CTRL);
+	int ret = -EINVAL;
+
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_IIC);
+
+	if (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;
+	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 solo6010_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?
+
+	down(&solo_dev->i2c_sem);
+	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);
+	solo6010_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;
+
+	up(&solo_dev->i2c_sem);
+
+	return ret;
+}
+
+static u32 solo_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm solo_i2c_algo = {
+	.master_xfer	= solo_i2c_master_xfer,
+	.functionality	= solo_i2c_functionality,
+};
+
+int solo_i2c_init(struct solo6010_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);
+	init_MUTEX(&solo_dev->i2c_sem);
+
+	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",
+			 SOLO6010_NAME, i);
+		adap->algo = &solo_i2c_algo;
+		adap->algo_data = solo_dev;
+		adap->retries = 1;
+		adap->dev.parent = &solo_dev->pdev->dev;
+
+		if ((ret = i2c_add_adapter(adap))) {
+			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;
+	}
+
+	dev_info(&solo_dev->pdev->dev, "Enabled %d i2c adapters\n",
+		 SOLO_I2C_ADAPTERS);
+
+	return 0;
+}
+
+void solo_i2c_exit(struct solo6010_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/staging/solo6x10/solo6010-jpeg.h b/drivers/staging/solo6x10/solo6010-jpeg.h
new file mode 100644
index 0000000..fb0507e
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-jpeg.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_JPEG_H
+#define __SOLO6010_JPEG_H
+
+static unsigned char 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
+
+#endif /* __SOLO6010_JPEG_H */
diff --git a/drivers/staging/solo6x10/solo6010-offsets.h b/drivers/staging/solo6x10/solo6010-offsets.h
new file mode 100644
index 0000000..2431de9
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-offsets.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_OFFSETS_H
+#define __SOLO6010_OFFSETS_H
+
+/* Offsets and sizes of the external address */
+#define SOLO_DISP_EXT_ADDR(__solo)		0x00000000
+#define SOLO_DISP_EXT_SIZE			0x00480000
+
+#define SOLO_DEC2LIVE_EXT_ADDR(__solo) \
+		(SOLO_DISP_EXT_ADDR(__solo) + SOLO_DISP_EXT_SIZE)
+#define SOLO_DEC2LIVE_EXT_SIZE			0x00240000
+
+#define SOLO_OSG_EXT_ADDR(__solo) \
+		(SOLO_DEC2LIVE_EXT_ADDR(__solo) + SOLO_DEC2LIVE_EXT_SIZE)
+#define SOLO_OSG_EXT_SIZE			0x00120000
+
+#define SOLO_EOSD_EXT_ADDR(__solo) \
+		(SOLO_OSG_EXT_ADDR(__solo) + SOLO_OSG_EXT_SIZE)
+#define SOLO_EOSD_EXT_SIZE			0x00010000
+
+#define SOLO_MOTION_EXT_ADDR(__solo) \
+		(SOLO_EOSD_EXT_ADDR(__solo) + \
+		 (SOLO_EOSD_EXT_SIZE * __solo->nr_chans))
+#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)
+#define SOLO_CAP_EXT_MAX_PAGE			(18 + 15)
+#define SOLO_CAP_EXT_SIZE			(SOLO_CAP_EXT_MAX_PAGE * 65536)
+
+/* This +1 is very important -- Why?! -- BenC */
+#define SOLO_EREF_EXT_ADDR(__solo) \
+		(SOLO_CAP_EXT_ADDR(__solo) + \
+		 (SOLO_CAP_EXT_SIZE * (__solo->nr_chans + 1)))
+#define SOLO_EREF_EXT_SIZE			0x00140000
+
+#define SOLO_MP4E_EXT_ADDR(__solo) \
+		(SOLO_EREF_EXT_ADDR(__solo) + \
+		 (SOLO_EREF_EXT_SIZE * __solo->nr_chans))
+#define SOLO_MP4E_EXT_SIZE(__solo)		(0x00080000 * __solo->nr_chans)
+
+#define SOLO_DREF_EXT_ADDR(__solo) \
+		(SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo))
+#define SOLO_DREF_EXT_SIZE			0x00140000
+
+#define SOLO_MP4D_EXT_ADDR(__solo) \
+		(SOLO_DREF_EXT_ADDR(__solo) + \
+		 (SOLO_DREF_EXT_SIZE * __solo->nr_chans))
+#define SOLO_MP4D_EXT_SIZE			0x00080000
+
+#define SOLO_JPEG_EXT_ADDR(__solo) \
+		(SOLO_MP4D_EXT_ADDR(__solo) + \
+		 (SOLO_MP4D_EXT_SIZE * __solo->nr_chans))
+#define SOLO_JPEG_EXT_SIZE(__solo)		(0x00080000 * __solo->nr_chans)
+
+#endif /* __SOLO6010_OFFSETS_H */
diff --git a/drivers/staging/solo6x10/solo6010-osd-font.h b/drivers/staging/solo6x10/solo6010-osd-font.h
new file mode 100644
index 0000000..d6f565b
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-osd-font.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_OSD_FONT_H
+#define __SOLO6010_OSD_FONT_H
+
+static const unsigned int solo_osd_font[] = {
+	0x00000000, 0x0000c0c8, 0xccfefe0c, 0x08000000,
+	0x00000000, 0x10103838, 0x7c7cfefe, 0x00000000,	// 0
+	0x00000000, 0xfefe7c7c, 0x38381010, 0x10000000,
+	0x00000000, 0x7c82fefe, 0xfefefe7c, 0x00000000,
+	0x00000000, 0x00001038, 0x10000000, 0x00000000,
+	0x00000000, 0x0010387c, 0xfe7c3810, 0x00000000,
+	0x00000000, 0x00384444, 0x44380000, 0x00000000,
+	0x00000000, 0x38448282, 0x82443800, 0x00000000,
+	0x00000000, 0x007c7c7c, 0x7c7c0000, 0x00000000,
+	0x00000000, 0x6c6c6c6c, 0x6c6c6c6c, 0x00000000,
+	0x00000000, 0x061e7efe, 0xfe7e1e06, 0x00000000,
+	0x00000000, 0xc0f0fcfe, 0xfefcf0c0, 0x00000000,
+	0x00000000, 0xc6cedefe, 0xfedecec6, 0x00000000,
+	0x00000000, 0xc6e6f6fe, 0xfef6e6c6, 0x00000000,
+	0x00000000, 0x12367efe, 0xfe7e3612, 0x00000000,
+	0x00000000, 0x90d8fcfe, 0xfefcd890, 0x00000000,
+	0x00000038, 0x7cc692ba, 0x92c67c38, 0x00000000,
+	0x00000038, 0x7cc6aa92, 0xaac67c38, 0x00000000,
+	0x00000038, 0x7830107c, 0xbaa8680c, 0x00000000,
+	0x00000038, 0x3c18127c, 0xb8382c60, 0x00000000,
+	0x00000044, 0xaa6c8254, 0x38eec67c, 0x00000000,
+	0x00000082, 0x44288244, 0x38c6827c, 0x00000000,
+	0x00000038, 0x444444fe, 0xfeeec6fe, 0x00000000,
+	0x00000018, 0x78187818, 0x3c7e7e3c, 0x00000000,
+	0x00000000, 0x3854929a, 0x82443800, 0x00000000,
+	0x00000000, 0x00c0c8cc, 0xfefe0c08, 0x00000000,
+	0x0000e0a0, 0xe040e00e, 0x8a0ea40e, 0x00000000,
+	0x0000e0a0, 0xe040e00e, 0x0a8e440e, 0x00000000,
+	0x0000007c, 0x82829292, 0x929282fe, 0x00000000,
+	0x000000f8, 0xfc046494, 0x946404fc, 0x00000000,
+	0x0000003f, 0x7f404c52, 0x524c407f, 0x00000000,
+	0x0000007c, 0x82ba82ba, 0x82ba82fe, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+	0x00000000, 0x183c3c3c, 0x18180018, 0x18000000,	// 32   !
+	0x00000066, 0x66240000, 0x00000000, 0x00000000,
+	0x00000000, 0x6c6cfe6c, 0x6c6cfe6c, 0x6c000000,	// 34 " #
+	0x00001010, 0x7cd6d616, 0x7cd0d6d6, 0x7c101000,
+	0x00000000, 0x0086c660, 0x30180cc6, 0xc2000000,	// 36 $ %
+	0x00000000, 0x386c6c38, 0xdc766666, 0xdc000000,
+	0x0000000c, 0x0c0c0600, 0x00000000, 0x00000000,	// 38 & '
+	0x00000000, 0x30180c0c, 0x0c0c0c18, 0x30000000,
+	0x00000000, 0x0c183030, 0x30303018, 0x0c000000,	// 40 ( )
+	0x00000000, 0x0000663c, 0xff3c6600, 0x00000000,
+	0x00000000, 0x00001818, 0x7e181800, 0x00000000,	// 42 * +
+	0x00000000, 0x00000000, 0x00000e0e, 0x0c060000,
+	0x00000000, 0x00000000, 0x7e000000, 0x00000000,	// 44 , -
+	0x00000000, 0x00000000, 0x00000006, 0x06000000,
+	0x00000000, 0x80c06030, 0x180c0602, 0x00000000,	// 46 . /
+	0x0000007c, 0xc6e6f6de, 0xcec6c67c, 0x00000000,
+	0x00000030, 0x383c3030, 0x303030fc, 0x00000000,	// 48 0 1
+	0x0000007c, 0xc6c06030, 0x180cc6fe, 0x00000000,
+	0x0000007c, 0xc6c0c07c, 0xc0c0c67c, 0x00000000,	// 50 2 3
+	0x00000060, 0x70786c66, 0xfe6060f0, 0x00000000,
+	0x000000fe, 0x0606067e, 0xc0c0c67c, 0x00000000,	// 52 4 5
+	0x00000038, 0x0c06067e, 0xc6c6c67c, 0x00000000,
+	0x000000fe, 0xc6c06030, 0x18181818, 0x00000000,	// 54 6 7
+	0x0000007c, 0xc6c6c67c, 0xc6c6c67c, 0x00000000,
+	0x0000007c, 0xc6c6c6fc, 0xc0c06038, 0x00000000,	// 56 8 9
+	0x00000000, 0x18180000, 0x00181800, 0x00000000,
+	0x00000000, 0x18180000, 0x0018180c, 0x00000000,	// 58 : ;
+	0x00000060, 0x30180c06, 0x0c183060, 0x00000000,
+	0x00000000, 0x007e0000, 0x007e0000, 0x00000000,
+	0x00000006, 0x0c183060, 0x30180c06, 0x00000000,
+	0x0000007c, 0xc6c66030, 0x30003030, 0x00000000,
+	0x0000007c, 0xc6f6d6d6, 0x7606067c, 0x00000000,
+	0x00000010, 0x386cc6c6, 0xfec6c6c6, 0x00000000,	// 64 @ A
+	0x0000007e, 0xc6c6c67e, 0xc6c6c67e, 0x00000000,
+	0x00000078, 0xcc060606, 0x0606cc78, 0x00000000,	// 66
+	0x0000003e, 0x66c6c6c6, 0xc6c6663e, 0x00000000,
+	0x000000fe, 0x0606063e, 0x060606fe, 0x00000000,	// 68
+	0x000000fe, 0x0606063e, 0x06060606, 0x00000000,
+	0x00000078, 0xcc060606, 0xf6c6ccb8, 0x00000000,	// 70
+	0x000000c6, 0xc6c6c6fe, 0xc6c6c6c6, 0x00000000,
+	0x0000003c, 0x18181818, 0x1818183c, 0x00000000,	// 72
+	0x00000060, 0x60606060, 0x6066663c, 0x00000000,
+	0x000000c6, 0xc666361e, 0x3666c6c6, 0x00000000,	// 74
+	0x00000006, 0x06060606, 0x060606fe, 0x00000000,
+	0x000000c6, 0xeefed6c6, 0xc6c6c6c6, 0x00000000,	// 76
+	0x000000c6, 0xcedefef6, 0xe6c6c6c6, 0x00000000,
+	0x00000038, 0x6cc6c6c6, 0xc6c66c38, 0x00000000,	// 78
+	0x0000007e, 0xc6c6c67e, 0x06060606, 0x00000000,
+	0x00000038, 0x6cc6c6c6, 0xc6d67c38, 0x60000000,	// 80
+	0x0000007e, 0xc6c6c67e, 0x66c6c6c6, 0x00000000,
+	0x0000007c, 0xc6c60c38, 0x60c6c67c, 0x00000000,	// 82
+	0x0000007e, 0x18181818, 0x18181818, 0x00000000,
+	0x000000c6, 0xc6c6c6c6, 0xc6c6c67c, 0x00000000,	// 84
+	0x000000c6, 0xc6c6c6c6, 0xc66c3810, 0x00000000,
+	0x000000c6, 0xc6c6c6c6, 0xd6d6fe6c, 0x00000000,	// 86
+	0x000000c6, 0xc6c66c38, 0x6cc6c6c6, 0x00000000,
+	0x00000066, 0x66666666, 0x3c181818, 0x00000000,	// 88
+	0x000000fe, 0xc0603018, 0x0c0606fe, 0x00000000,
+	0x0000003c, 0x0c0c0c0c, 0x0c0c0c3c, 0x00000000,	// 90
+	0x00000002, 0x060c1830, 0x60c08000, 0x00000000,
+	0x0000003c, 0x30303030, 0x3030303c, 0x00000000,	// 92
+	0x00001038, 0x6cc60000, 0x00000000, 0x00000000,
+	0x00000000, 0x00000000, 0x00000000, 0x00fe0000,
+	0x00001818, 0x30000000, 0x00000000, 0x00000000,
+	0x00000000, 0x00003c60, 0x7c66667c, 0x00000000,
+	0x0000000c, 0x0c0c7ccc, 0xcccccc7c, 0x00000000,
+	0x00000000, 0x00007cc6, 0x0606c67c, 0x00000000,
+	0x00000060, 0x60607c66, 0x6666667c, 0x00000000,
+	0x00000000, 0x00007cc6, 0xfe06c67c, 0x00000000,
+	0x00000078, 0x0c0c0c3e, 0x0c0c0c0c, 0x00000000,
+	0x00000000, 0x00007c66, 0x6666667c, 0x60603e00,
+	0x0000000c, 0x0c0c7ccc, 0xcccccccc, 0x00000000,
+	0x00000030, 0x30003830, 0x30303078, 0x00000000,
+	0x00000030, 0x30003c30, 0x30303030, 0x30301f00,
+	0x0000000c, 0x0c0ccc6c, 0x3c6ccccc, 0x00000000,
+	0x00000030, 0x30303030, 0x30303030, 0x00000000,
+	0x00000000, 0x000066fe, 0xd6d6d6d6, 0x00000000,
+	0x00000000, 0x000078cc, 0xcccccccc, 0x00000000,
+	0x00000000, 0x00007cc6, 0xc6c6c67c, 0x00000000,
+	0x00000000, 0x00007ccc, 0xcccccc7c, 0x0c0c0c00,
+	0x00000000, 0x00007c66, 0x6666667c, 0x60606000,
+	0x00000000, 0x000076dc, 0x0c0c0c0c, 0x00000000,
+	0x00000000, 0x00007cc6, 0x1c70c67c, 0x00000000,
+	0x00000000, 0x1818fe18, 0x18181870, 0x00000000,
+	0x00000000, 0x00006666, 0x6666663c, 0x00000000,
+	0x00000000, 0x0000c6c6, 0xc66c3810, 0x00000000,
+	0x00000000, 0x0000c6d6, 0xd6d6fe6c, 0x00000000,
+	0x00000000, 0x0000c66c, 0x38386cc6, 0x00000000,
+	0x00000000, 0x00006666, 0x6666667c, 0x60603e00,
+	0x00000000, 0x0000fe60, 0x30180cfe, 0x00000000,
+	0x00000070, 0x1818180e, 0x18181870, 0x00000000,
+	0x00000018, 0x18181800, 0x18181818, 0x00000000,
+	0x0000000e, 0x18181870, 0x1818180e, 0x00000000,
+	0x000000dc, 0x76000000, 0x00000000, 0x00000000,
+	0x00000000, 0x0010386c, 0xc6c6fe00, 0x00000000
+};
+
+#endif /* __SOLO6010_OSD_FONT_H */
diff --git a/drivers/staging/solo6x10/solo6010-p2m.c b/drivers/staging/solo6x10/solo6010-p2m.c
new file mode 100644
index 0000000..1b81f06
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-p2m.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+
+#include "solo6010.h"
+
+// #define SOLO_TEST_P2M
+
+int solo_p2m_dma(struct solo6010_dev *solo_dev, u8 id, int wr,
+		 void *sys_addr, u32 ext_addr, u32 size)
+{
+	dma_addr_t dma_addr;
+	int ret;
+
+	WARN_ON(!size);
+	WARN_ON(id >= SOLO_NR_P2M);
+	if (!size || id >= SOLO_NR_P2M)
+		return -EINVAL;
+
+	dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size,
+				  wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+
+	ret = solo_p2m_dma_t(solo_dev, id, wr, dma_addr, ext_addr, size);
+
+	pci_unmap_single(solo_dev->pdev, dma_addr, size,
+			 wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+
+	return ret;
+}
+
+int solo_p2m_dma_t(struct solo6010_dev *solo_dev, u8 id, int wr,
+		   dma_addr_t dma_addr, u32 ext_addr, u32 size)
+{
+	struct solo_p2m_dev *p2m_dev;
+	unsigned int timeout = 0;
+
+	WARN_ON(!size);
+	WARN_ON(id >= SOLO_NR_P2M);
+	if (!size || id >= SOLO_NR_P2M)
+		return -EINVAL;
+
+	p2m_dev = &solo_dev->p2m_dev[id];
+
+	down(&p2m_dev->sem);
+
+start_dma:
+	INIT_COMPLETION(p2m_dev->completion);
+	p2m_dev->error = 0;
+	solo_reg_write(solo_dev, SOLO_P2M_TAR_ADR(id), dma_addr);
+	solo_reg_write(solo_dev, SOLO_P2M_EXT_ADR(id), ext_addr);
+	solo_reg_write(solo_dev, SOLO_P2M_EXT_CFG(id),
+		       SOLO_P2M_COPY_SIZE(size >> 2));
+	solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id),
+		       SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) |
+		       (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON);
+
+	timeout = wait_for_completion_timeout(&p2m_dev->completion, HZ);
+
+	solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0);
+
+	/* XXX Really looks to me like we will get stuck here if a
+	 * real PCI P2M error occurs */
+	if (p2m_dev->error)
+		goto start_dma;
+
+	up(&p2m_dev->sem);
+
+	return (timeout == 0) ? -EAGAIN : 0;
+}
+
+#ifdef SOLO_TEST_P2M
+
+#define P2M_TEST_CHAR		0xbe
+
+static unsigned long long p2m_test(struct solo6010_dev *solo_dev, u8 id,
+				   u32 base, int size)
+{
+	u8 *wr_buf;
+	u8 *rd_buf;
+	int i;
+	unsigned long long err_cnt = 0;
+
+	wr_buf = kmalloc(size, GFP_KERNEL);
+	if (!wr_buf) {
+		printk(SOLO6010_NAME ": Failed to malloc for p2m_test\n");
+		return size;
+	}
+
+	rd_buf = kmalloc(size, GFP_KERNEL);
+	if (!rd_buf) {
+		printk(SOLO6010_NAME ": Failed to malloc for p2m_test\n");
+		kfree(wr_buf);
+		return size;
+	}
+
+	memset(wr_buf, P2M_TEST_CHAR, size);
+	memset(rd_buf, P2M_TEST_CHAR + 1, size);
+
+	solo_p2m_dma(solo_dev, id, 1, wr_buf, base, size);
+	solo_p2m_dma(solo_dev, id, 0, rd_buf, base, size);
+
+	for (i = 0; i < size; i++)
+		if (wr_buf[i] != rd_buf[i])
+			err_cnt++;
+
+	kfree(wr_buf);
+	kfree(rd_buf);
+
+	return err_cnt;
+}
+
+#define TEST_CHUNK_SIZE		(8 * 1024)
+
+static void run_p2m_test(struct solo6010_dev *solo_dev)
+{
+	unsigned long long errs = 0;
+	u32 size = SOLO_JPEG_EXT_ADDR(solo_dev) + SOLO_JPEG_EXT_SIZE(solo_dev);
+	int i, d;
+
+	printk(KERN_WARNING "%s: Testing %u bytes of external ram\n",
+	       SOLO6010_NAME, size);
+
+	for (i = 0; i < size; i += TEST_CHUNK_SIZE)
+		for (d = 0; d < 4; d++)
+			errs += p2m_test(solo_dev, d, i, TEST_CHUNK_SIZE);
+
+	printk(KERN_WARNING "%s: Found %llu errors during p2m test\n",
+	       SOLO6010_NAME, errs);
+
+	return;
+}
+#else
+#define run_p2m_test(__solo)	do{}while(0)
+#endif
+
+void solo_p2m_isr(struct solo6010_dev *solo_dev, int id)
+{
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_P2M(id));
+	complete(&solo_dev->p2m_dev[id].completion);
+}
+
+void solo_p2m_error_isr(struct solo6010_dev *solo_dev, u32 status)
+{
+	struct solo_p2m_dev *p2m_dev;
+	int i;
+
+	if (!(status & SOLO_PCI_ERR_P2M))
+		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 solo6010_dev *solo_dev)
+{
+	int i;
+
+	for (i = 0; i < SOLO_NR_P2M; i++)
+		solo6010_irq_off(solo_dev, SOLO_IRQ_P2M(i));
+}
+
+int solo_p2m_init(struct solo6010_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];
+
+		init_MUTEX(&p2m_dev->sem);
+		init_completion(&p2m_dev->completion);
+
+		solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(i),
+			       __pa(p2m_dev->desc));
+
+		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_DMA_INTERVAL(0) |
+			       SOLO_P2M_PCI_MASTER_MODE);
+		solo6010_irq_on(solo_dev, SOLO_IRQ_P2M(i));
+	}
+
+	run_p2m_test(solo_dev);
+
+	return 0;
+}
diff --git a/drivers/staging/solo6x10/solo6010-registers.h b/drivers/staging/solo6x10/solo6010-registers.h
new file mode 100644
index 0000000..d39d3c6
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-registers.h
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_REGISTERS_H
+#define __SOLO6010_REGISTERS_H
+
+#include "solo6010-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	(1<<5)
+#define	  SOLO_DMA_CTRL_STROBE_SELECT		(1<<4)
+#define	  SOLO_DMA_CTRL_READ_DATA_SELECT	(1<<3)
+#define	  SOLO_DMA_CTRL_READ_CLK_SELECT		(1<<2)
+#define	  SOLO_DMA_CTRL_LATENCY(n)		((n)<<0)
+
+#define SOLO_SYS_VCLK				0x000C
+#define	  SOLO_VCLK_INVERT			(1<<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_ENABLE				0x0014
+#define	  SOLO_IRQ_P2M(n)			(1<<((n)+17))
+#define	  SOLO_IRQ_GPIO				(1<<16)
+#define	  SOLO_IRQ_VIDEO_LOSS			(1<<15)
+#define	  SOLO_IRQ_VIDEO_IN			(1<<14)
+#define	  SOLO_IRQ_MOTION			(1<<13)
+#define	  SOLO_IRQ_ATA_CMD			(1<<12)
+#define	  SOLO_IRQ_ATA_DIR			(1<<11)
+#define	  SOLO_IRQ_PCI_ERR			(1<<10)
+#define	  SOLO_IRQ_PS2_1			(1<<9)
+#define	  SOLO_IRQ_PS2_0			(1<<8)
+#define	  SOLO_IRQ_SPI				(1<<7)
+#define	  SOLO_IRQ_IIC				(1<<6)
+#define	  SOLO_IRQ_UART(n)			(1<<((n) + 4))
+#define	  SOLO_IRQ_G723				(1<<3)
+#define	  SOLO_IRQ_DECODER			(1<<1)
+#define	  SOLO_IRQ_ENCODER			(1<<0)
+
+#define SOLO_CHIP_OPTION			0x001C
+#define   SOLO_CHIP_ID_MASK			0x00000007
+
+#define SOLO_EEPROM_CTRL			0x0060
+#define	  SOLO_EEPROM_ACCESS_EN			(1<<7)
+#define	  SOLO_EEPROM_CS			(1<<3)
+#define	  SOLO_EEPROM_CLK			(1<<2)
+#define	  SOLO_EEPROM_DO			(1<<1)
+#define	  SOLO_EEPROM_DI			(1<<0)
+#define	  SOLO_EEPROM_ENABLE			(EEPROM_ACCESS_EN | 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		(1<<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		(1<<4)
+#define	  SOLO_P2M_UV_SWAP			(1<<3)
+#define	  SOLO_P2M_PCI_MASTER_MODE		(1<<2)
+#define	  SOLO_P2M_DESC_INTR_OPT		(1<<1)	/* 1:Empty, 0:Each */
+#define	  SOLO_P2M_DESC_MODE			(1<<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			(1<<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			(1<<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			(1<<3)
+#define	  SOLO_P2M_INTERRUPT_REQ		(1<<2)
+#define	  SOLO_P2M_WRITE			(1<<1)
+#define	  SOLO_P2M_TRANS_ON			(1<<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		(1<<31)
+#define	  SOLO_VI_FMT_CHECK_HCOUNT		(1<<30)
+#define   SOLO_VI_FMT_TEST_SIGNAL		(1<<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			(1<<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			(1<<1)
+#define	  SOLO_VI_PB_PAL			(1<<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	(1<<15)
+#define	  SOLO_VI_MOTION_FREEZE_DATA		(1<<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			(1<<29)
+#define	  SOLO_VI_MOTION_Y_ADD			(1<<28)
+#define	  SOLO_VI_MOTION_CB_SET			(1<<27)
+#define	  SOLO_VI_MOTION_CB_ADD			(1<<26)
+#define	  SOLO_VI_MOTION_CR_SET			(1<<25)
+#define	  SOLO_VI_MOTION_CR_ADD			(1<<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		(1<<31)
+#define	  SOLO_VO_FMT_TYPE_PAL			(1<<30)
+#define   SOLO_VO_FMT_TYPE_NTSC			0
+#define	  SOLO_VO_USER_SET			(1<<29)
+
+#define	  SOLO_VO_FI_CHANGE			(1<<20)
+#define	  SOLO_VO_USER_COLOR_SET_VSYNC		(1<<19)
+#define	  SOLO_VO_USER_COLOR_SET_HSYNC		(1<<18)
+#define	  SOLO_VO_USER_COLOR_SET_NAV		(1<<17)
+#define	  SOLO_VO_USER_COLOR_SET_NAH		(1<<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			(1<<24)
+#define	  SOLO_VO_HSYNC_INVERT			(1<<23)
+#define	  SOLO_VO_VSYNC_INVERT			(1<<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			(1<<31)
+#define	  SOLO_VO_DISP_ERASE_COUNT(n)		((n&0xf)<<24)
+#define	  SOLO_VO_DISP_DOUBLE_SCAN		(1<<22)
+#define	  SOLO_VO_DISP_SINGLE_PAGE		(1<<21)
+#define	  SOLO_VO_DISP_BASE(n)			(((n)>>16) & 0xffff)
+
+#define SOLO_VO_DISP_ERASE			0x0314
+#define	  SOLO_VO_DISP_ERASE_ON			(1<<0)
+
+#define	SOLO_VO_ZOOM_CTRL			0x0318
+#define	  SOLO_VO_ZOOM_VER_ON			(1<<24)
+#define	  SOLO_VO_ZOOM_HOR_ON			(1<<23)
+#define	  SOLO_VO_ZOOM_V_COMP			(1<<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			(1<<1)
+#define	  SOLO_VO_FREEZE_INTERPOLATION		(1<<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			(1<<31)
+#define	  SOLO_VO_OSG_COLOR_MUTE		(1<<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			(1<<1)
+#define	  SOLO_VO_OSG_BLINK_INTREVAL18		(1<<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			(1<<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			(1<<18)
+#define	  SOLO_VE_MOTION_MODE(n)		((n)<<16)
+#define	  SOLO_VE_MOTION_BASE(n)		((n)<<0)
+
+#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			(1<<31)
+#define	  SOLO_COMP_ATTR_RN			(1<<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_STATE(n)			(0x0640+((n)*4))
+struct videnc_status {
+	union {
+		u32 status0;
+		struct {
+			u32 mp4_enc_code_size:20, sad_motion:1, vid_motion:1,
+			    vop_type:2, video_channel:5, source_field_idx:1,
+			    interlace:1, progressive:1;
+		} status0_st;
+	};
+	union {
+		u32 status1;
+		struct {
+			u32 vsize:8, hsize:8, last_queue:4, foo1:8, scale:4;
+		} status1_st;
+	};
+	union {
+		u32 status4;
+		struct {
+			u32 jpeg_code_size:20, interval:10, foo1:2;
+		} status4_st;
+	};
+	union {
+		u32 status9;
+		struct {
+			u32 channel:5, foo1:27;
+		} status9_st;
+	};
+	union {
+		u32 status10;
+		struct {
+			u32 mp4_code_size:20, foo:12;
+		} status10_st;
+	};
+	union {
+		u32 status11;
+		struct {
+			u32 last_queue:8, foo1:24;
+		} status11_st;
+	};
+};
+
+#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_OSD_CH				0x0690
+#define SOLO_VE_OSD_BASE			0x0694
+#define SOLO_VE_OSD_CLR				0x0698
+#define SOLO_VE_OSD_OPT				0x069C
+
+#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	(1<<24)
+#define	  SOLO_VD_CFG_BUSY_WIAT_CODE		(1<<23)
+#define	  SOLO_VD_CFG_BUSY_WIAT_REF		(1<<22)
+#define	  SOLO_VD_CFG_BUSY_WIAT_RES		(1<<21)
+#define	  SOLO_VD_CFG_BUSY_WIAT_MS		(1<<20)
+#define	  SOLO_VD_CFG_SINGLE_MODE		(1<<18)
+#define	  SOLO_VD_CFG_SCAL_MANUAL		(1<<17)
+#define	  SOLO_VD_CFG_USER_PAGE_CTRL		(1<<16)
+#define	  SOLO_VD_CFG_LITTLE_ENDIAN		(1<<15)
+#define	  SOLO_VD_CFG_START_FI			(1<<14)
+#define	  SOLO_VD_CFG_ERR_LOCK			(1<<13)
+#define	  SOLO_VD_CFG_ERR_INT_ENA		(1<<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			(1<<31)
+#define	  SOLO_VD_MAX_ITEM(n)			((n)<<0)
+
+#define SOLO_VD_STATUS0				0x0920
+#define	  SOLO_VD_STATUS0_INTR_ACK		(1<<22)
+#define	  SOLO_VD_STATUS0_INTR_EMPTY		(1<<21)
+#define	  SOLO_VD_STATUS0_INTR_ERR		(1<<20)
+
+#define SOLO_VD_STATUS1				0x0924
+
+#define SOLO_VD_IDX0				0x0930
+#define	  SOLO_VD_IDX_INTERLACE			(1<<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		(1<<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		(1<<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		(1<<27)
+#define	  SOLO_VD_IDX_INTERPOL			(1<<26)
+#define	  SOLO_VD_IDX_HOR2X			(1<<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			(1<<8)
+#define	  SOLO_IIC_PRESCALE(n)			((n)<<0)
+
+#define SOLO_IIC_CTRL				0x0B24
+#define	  SOLO_IIC_AUTO_CLEAR			(1<<20)
+#define	  SOLO_IIC_STATE_RX_ACK			(1<<19)
+#define	  SOLO_IIC_STATE_BUSY			(1<<18)
+#define	  SOLO_IIC_STATE_SIG_ERR		(1<<17)
+#define	  SOLO_IIC_STATE_TRNS			(1<<16)
+#define	  SOLO_IIC_CH_SET(n)			((n)<<5)
+#define	  SOLO_IIC_ACK_EN			(1<<4)
+#define	  SOLO_IIC_START			(1<<3)
+#define	  SOLO_IIC_STOP				(1<<2)
+#define	  SOLO_IIC_READ				(1<<1)
+#define	  SOLO_IIC_WRITE			(1<<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			(1<<20)
+#define	  SOLO_PARITY_ERROR_DROP		(1<<18)
+#define	  SOLO_IRQ_ERR_EN			(1<<17)
+#define	  SOLO_IRQ_RX_EN			(1<<16)
+#define	  SOLO_IRQ_TX_EN			(1<<15)
+#define	  SOLO_RX_EN				(1<<14)
+#define	  SOLO_TX_EN				(1<<13)
+#define	  SOLO_UART_HALF_DUPLEX			(1<<12)
+#define	  SOLO_UART_LOOPBACK			(1<<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				(1<<15)
+#define	  SOLO_UART_RX_BUSY			(1<<14)
+#define	  SOLO_UART_OVERRUN			(1<<13)
+#define	  SOLO_UART_FRAME_ERR			(1<<12)
+#define	  SOLO_UART_PARITY_ERR			(1<<11)
+#define	  SOLO_UART_TX_BUSY			(1<<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		(1<<8)
+#define SOLO_UART_RX_DATA(n)			(0x0BAC + ((n)*0x20))
+#define	  SOLO_UART_RX_DATA_POP			(1<<8)
+
+#define SOLO_TIMER_CLOCK_NUM			0x0be0
+#define SOLO_TIMER_WATCHDOG			0x0be4
+#define SOLO_TIMER_USEC				0x0be8
+#define SOLO_TIMER_SEC				0x0bec
+
+#define SOLO_AUDIO_CONTROL			0x0D00
+#define	  SOLO_AUDIO_ENABLE			(1<<31)
+#define	  SOLO_AUDIO_MASTER_MODE		(1<<30)
+#define	  SOLO_AUDIO_I2S_MODE			(1<<29)
+#define	  SOLO_AUDIO_I2S_LR_SWAP		(1<<27)
+#define	  SOLO_AUDIO_I2S_8BIT			(1<<26)
+#define	  SOLO_AUDIO_I2S_MULTI(n)		((n)<<24)
+#define	  SOLO_AUDIO_MIX_9TO0			(1<<23)
+#define	  SOLO_AUDIO_DEC_9TO0_VOL(n)		((n)<<20)
+#define	  SOLO_AUDIO_MIX_19TO10			(1<<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			(1<<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
+
+
+#define SOLO_WATCHDOG				0x0BE4
+#define WATCHDOG_STAT(status)			(status<<8)
+#define WATCHDOG_TIME(sec)			(sec&0xff)
+
+#endif /* __SOLO6010_REGISTERS_H */
diff --git a/drivers/staging/solo6x10/solo6010-tw28.c b/drivers/staging/solo6x10/solo6010-tw28.c
new file mode 100644
index 0000000..b4446b9
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-tw28.c
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+
+#include "solo6010.h"
+#include "solo6010-tw28.h"
+
+/* XXX: Some of these values are masked into an 8-bit regs, and shifted
+ * around for other 8-bit regs. What are the magic bits in these values? */
+#define DEFAULT_HDELAY_NTSC		(32 - 4)
+#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 u8 tbl_tw2864_template[] = {
+	0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, // 0x00
+	0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+	0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, // 0x10
+	0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+	0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, // 0x20
+	0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f,
+	0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, // 0x30
+	0x12, 0xf5, 0x09, 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, 0xd1, 0x0f, 0x11, 0x11, 0x81,
+	0x10, 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,
+};
+
+#define is_tw286x(__solo, __id) (!(__solo->tw2815 & (1 << __id)))
+
+static u8 tw_readbyte(struct solo6010_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 solo6010_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 solo6010_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("solo6010/tw28: Error writing register: %02x->%02x [%02x]\n",
+		addr, off, val);
+}
+
+static int tw2864_setup(struct solo6010_dev *solo_dev, u8 dev_addr)
+{
+	u8 tbl_tw2864_common[256];
+	int i;
+
+	memcpy(tbl_tw2864_common, tbl_tw2864_template,
+	       sizeof(tbl_tw2864_common));
+
+	/* 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;
+	}
+
+	/* NTSC or PAL */
+	if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) {
+		for (i = 0; i < 4; i++) {
+			tbl_tw2864_common[0x07 | (i << 4)] |= 0x10;
+			tbl_tw2864_common[0x08 | (i << 4)] |= 0x06;
+			tbl_tw2864_common[0x0a | (i << 4)] |= 0x08;
+			tbl_tw2864_common[0x0b | (i << 4)] |= 0x13;
+			tbl_tw2864_common[0x0e | (i << 4)] |= 0x01;
+		}
+		tbl_tw2864_common[0x9d] = 0x90;
+		tbl_tw2864_common[0xf3] = 0x00;
+		tbl_tw2864_common[0xf4] = 0xa0;
+	}
+
+	for (i = 0; i < 0xff; i++) {
+		/* Skip read only registers */
+		if (i >= 0xb8 && i <= 0xc1 )
+			continue;
+		if ((i & ~0x30) == 0x00 ||
+		    (i & ~0x30) == 0x0c ||
+		    (i & ~0x30) == 0x0d)
+			continue;
+		if (i == 0x74 || i == 0x77 || i == 0x78 ||
+		    i == 0x79 || i == 0x7a)
+			continue;
+                if (i == 0xfd)
+                        continue;
+
+		tw_write_and_verify(solo_dev, dev_addr, i,
+				    tbl_tw2864_common[i]);
+	}
+
+	return 0;
+}
+
+static int tw2815_setup(struct solo6010_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 saa7128_setup(struct solo6010_dev *solo_dev)
+{
+	int i;
+	unsigned char regs[128] = {
+		0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x1C, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00,
+		0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f, 0x00, 0x00,
+		0x1c, 0x33, 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x00,
+		0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18,
+		0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f,
+		0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06,
+		0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e,
+		0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77,
+		0x41, 0x88, 0x41, 0x12, 0xed, 0x10, 0x10, 0x00,
+		0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00,
+		0x00, 0x00, 0x08, 0xff, 0x80, 0x00, 0xff, 0xff,
+	};
+
+	regs[0x7A] = FIRST_ACTIVE_LINE & 0xff;
+	regs[0x7B] = LAST_ACTIVE_LINE & 0xff;
+	regs[0x7C] = ((1 << 7) |
+			(((LAST_ACTIVE_LINE >> 8) & 1) << 6) |
+			(((FIRST_ACTIVE_LINE >> 8) & 1) << 4));
+
+	/* PAL: XXX: We could do a second set of regs to avoid this */
+	if (solo_dev->video_type != SOLO_VO_FMT_TYPE_NTSC) {
+		regs[0x28] = 0xE1;
+
+		regs[0x5A] = 0x0F;
+		regs[0x61] = 0x02;
+		regs[0x62] = 0x35;
+		regs[0x63] = 0xCB;
+		regs[0x64] = 0x8A;
+		regs[0x65] = 0x09;
+		regs[0x66] = 0x2A;
+
+		regs[0x6C] = 0xf1;
+		regs[0x6E] = 0x20;
+
+		regs[0x7A] = 0x06 + 12;
+		regs[0x7b] = 0x24 + 12;
+		regs[0x7c] |= 1 << 6;
+	}
+
+	/* First 0x25 bytes are read-only? */
+	for (i = 0x26; i < 128; i++) {
+		if (i == 0x60 || i == 0x7D)
+			continue;
+		solo_i2c_writebyte(solo_dev, SOLO_I2C_SAA, 0x46, i, regs[i]);
+	}
+
+	return;
+}
+
+int solo_tw28_init(struct solo6010_dev *solo_dev)
+{
+	int i;
+	u8 value;
+
+	/* Detect techwell chip type */
+	for (i = 0; i < TW_NUM_CHIP; i++) {
+		value = solo_i2c_readbyte(solo_dev, SOLO_I2C_TW,
+					  TW_CHIP_OFFSET_ADDR(i), 0xFF);
+
+		switch (value >> 3) {
+		case 0x18:
+			printk("solo6010: 2865 support not enabled\n");
+			return -EINVAL;
+			break;
+		case 0x0c:
+			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)
+		return -EINVAL;
+
+	saa7128_setup(solo_dev);
+
+	for (i = 0; i < solo_dev->tw28_cnt; i++) {
+		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));
+	}
+
+	dev_info(&solo_dev->pdev->dev, "Initialized %d tw28xx chip%s:",
+		 solo_dev->tw28_cnt, solo_dev->tw28_cnt == 1 ? "" : "s");
+
+	if (solo_dev->tw2864)
+		printk(" tw2864[%d]", hweight32(solo_dev->tw2864));
+	if (solo_dev->tw2815)
+		printk(" tw2815[%d]", hweight32(solo_dev->tw2815));
+	printk("\n");
+
+	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 solo6010_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 solo6010_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
+
+int tw28_set_ctrl_val(struct solo6010_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 (val > 0x0f || val < 0)
+			return -ERANGE;
+		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 if (val != 0)
+			return -ERANGE;
+		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:
+		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 solo6010_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 solo6010_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 solo6010_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 solo6010_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/staging/solo6x10/solo6010-tw28.h b/drivers/staging/solo6x10/solo6010-tw28.h
new file mode 100644
index 0000000..a7eecfa
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-tw28.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_TW28_H
+#define __SOLO6010_TW28_H
+
+#include "solo6010.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 solo6010_dev *solo_dev);
+
+int tw28_set_ctrl_val(struct solo6010_dev *solo_dev, u32 ctrl, u8 ch,
+		      s32 val);
+int tw28_get_ctrl_val(struct solo6010_dev *solo_dev, u32 ctrl, u8 ch,
+		      s32 *val);
+
+u8 tw28_get_audio_gain(struct solo6010_dev *solo_dev, u8 ch);
+void tw28_set_audio_gain(struct solo6010_dev *solo_dev, u8 ch, u8 val);
+int tw28_get_video_status(struct solo6010_dev *solo_dev, u8 ch);
+
+#if 0
+unsigned int tw2815_get_audio_status(struct SOLO6010 *solo6010);
+void tw2815_Set_AudioOutVol(struct SOLO6010 *solo6010, unsigned int u_val);
+#endif
+
+#endif /* __SOLO6010_TW28_H */
diff --git a/drivers/staging/solo6x10/solo6010-v4l2-enc.c b/drivers/staging/solo6x10/solo6010-v4l2-enc.c
new file mode 100644
index 0000000..f114b4b
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-v4l2-enc.c
@@ -0,0 +1,1564 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#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/videobuf-dma-contig.h>
+
+#include "solo6010.h"
+#include "solo6010-tw28.h"
+#include "solo6010-jpeg.h"
+
+#define MIN_VID_BUFFERS		4
+#define FRAME_BUF_SIZE		(128 * 1024)
+#define MP4_QS			16
+
+static int solo_enc_thread(void *data);
+
+extern unsigned video_nr;
+
+struct solo_enc_fh {
+	struct			solo_enc_dev *enc;
+	u32			fmt;
+	u16			rd_idx;
+	u8			enc_on;
+	enum solo_enc_types	type;
+	struct videobuf_queue	vidq;
+	struct list_head	vidq_active;
+	struct task_struct	*kthread;
+};
+
+static unsigned char vid_vop_header[] = {
+	0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20,
+	0x02, 0x48, 0x05, 0xc0, 0x00, 0x40, 0x00, 0x40,
+	0x00, 0x40, 0x00, 0x80, 0x00, 0x97, 0x53, 0x04,
+	0x1f, 0x4c, 0x58, 0x10, 0x78, 0x51, 0x18, 0x3e,
+};
+
+/*
+ * Things we can change around:
+ *
+ * byte  10,        4-bits 01111000                   aspect
+ * bytes 21,22,23  16-bits 000x1111 11111111 1111x000 fps/res
+ * bytes 23,24,25  15-bits 00000n11 11111111 11111x00 interval
+ * bytes 25,26,27  13-bits 00000x11 11111111 111x0000 width
+ * bytes 27,28,29  13-bits 000x1111 11111111 1x000000 height
+ * byte  29         1-bit  0x100000                   interlace
+ */
+
+/* For aspect */
+#define XVID_PAR_43_PAL		2
+#define XVID_PAR_43_NTSC	3
+
+static const u32 solo_user_ctrls[] = {
+	V4L2_CID_BRIGHTNESS,
+	V4L2_CID_CONTRAST,
+	V4L2_CID_SATURATION,
+	V4L2_CID_HUE,
+	V4L2_CID_SHARPNESS,
+	0
+};
+
+static const u32 solo_mpeg_ctrls[] = {
+	V4L2_CID_MPEG_VIDEO_ENCODING,
+	V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+	0
+};
+
+static const u32 solo_private_ctrls[] = {
+	V4L2_CID_MOTION_ENABLE,
+	V4L2_CID_MOTION_THRESHOLD,
+	0
+};
+
+static const u32 solo_fmtx_ctrls[] = {
+	V4L2_CID_RDS_TX_RADIO_TEXT,
+	0
+};
+
+static const u32 *solo_ctrl_classes[] = {
+	solo_user_ctrls,
+	solo_mpeg_ctrls,
+	solo_fmtx_ctrls,
+	solo_private_ctrls,
+	NULL
+};
+
+struct vop_header {
+	/* VD_IDX0 */
+	u32 size:20, sync_start:1, page_stop:1, vop_type:2, channel:4,
+		nop0:1, source_fl:1, interlace:1, progressive:1;
+
+	/* VD_IDX1 */
+	u32 vsize:8, hsize:8, frame_interop:1, nop1:7, win_id:4, scale:4;
+
+	/* VD_IDX2 */
+	u32 base_addr:16, nop2:15, hoff:1;
+
+	/* VD_IDX3 - User set macros */
+	u32 sy:12, sx:12, nop3:1, hzoom:1, read_interop:1, write_interlace:1,
+		scale_mode:4;
+
+	/* VD_IDX4 - User set macros continued */
+	u32 write_page:8, nop4:24;
+
+	/* VD_IDX5 */
+	u32 next_code_addr;
+
+	u32 end_nops[10];
+} __attribute__((packed));
+
+static int solo_is_motion_on(struct solo_enc_dev *solo_enc)
+{
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	u8 ch = solo_enc->ch;
+
+	if (solo_dev->motion_mask & (1 << ch))
+		return 1;
+	return 0;
+}
+
+static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on)
+{
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	u8 ch = solo_enc->ch;
+
+	spin_lock(&solo_enc->lock);
+
+	if (on)
+		solo_dev->motion_mask |= (1 << ch);
+	else
+		solo_dev->motion_mask &= ~(1 << ch);
+
+	solo_reg_write(solo_dev, SOLO_VI_MOT_ADR,
+		       SOLO_VI_MOTION_EN(solo_dev->motion_mask) |
+		       (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16));
+
+	if (solo_dev->motion_mask)
+		solo6010_irq_on(solo_dev, SOLO_IRQ_MOTION);
+	else
+		solo6010_irq_off(solo_dev, SOLO_IRQ_MOTION);
+
+	spin_unlock(&solo_enc->lock);
+}
+
+/* Should be called with solo_enc->lock held */
+static void solo_update_mode(struct solo_enc_dev *solo_enc)
+{
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	assert_spin_locked(&solo_enc->lock);
+
+	solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0;
+	solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1);
+
+	switch (solo_enc->mode) {
+	case SOLO_ENC_MODE_CIF:
+		solo_enc->width = solo_dev->video_hsize >> 1;
+		solo_enc->height = solo_dev->video_vsize;
+		break;
+	case SOLO_ENC_MODE_D1:
+		solo_enc->width = solo_dev->video_hsize;
+		solo_enc->height = solo_dev->video_vsize << 1;
+		solo_enc->bw_weight <<= 2;
+		break;
+	default:
+		WARN(1, "mode is unknown");
+	}
+}
+
+/* Should be called with solo_enc->lock held */
+static int solo_enc_on(struct solo_enc_fh *fh)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	u8 ch = solo_enc->ch;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	u8 interval;
+
+	assert_spin_locked(&solo_enc->lock);
+
+	if (fh->enc_on)
+		return 0;
+
+	solo_update_mode(solo_enc);
+
+	/* Make sure to bw check on first reader */
+	if (!atomic_read(&solo_enc->readers)) {
+		if (solo_enc->bw_weight > solo_dev->enc_bw_remain)
+			return -EBUSY;
+		else
+			solo_dev->enc_bw_remain -= solo_enc->bw_weight;
+	}
+
+	fh->kthread = kthread_run(solo_enc_thread, fh, SOLO6010_NAME "_enc");
+
+	if (IS_ERR(fh->kthread))
+		return PTR_ERR(fh->kthread);
+
+	fh->enc_on = 1;
+	fh->rd_idx = solo_enc->solo_dev->enc_wr_idx;
+
+	if (fh->type == SOLO_ENC_TYPE_EXT)
+		solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1);
+
+	if (atomic_inc_return(&solo_enc->readers) > 1)
+		return 0;
+
+	/* 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);
+
+	/* Settle down Beavis... */
+	mdelay(10);
+
+	return 0;
+}
+
+static void solo_enc_off(struct solo_enc_fh *fh)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	if (!fh->enc_on)
+		return;
+
+	if (fh->kthread) {
+		kthread_stop(fh->kthread);
+		fh->kthread = NULL;
+	}
+
+	solo_dev->enc_bw_remain += solo_enc->bw_weight;
+	fh->enc_on = 0;
+
+	if (atomic_dec_return(&solo_enc->readers) > 0)
+		return;
+
+	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 void enc_reset_gop(struct solo6010_dev *solo_dev, u8 ch)
+{
+	BUG_ON(ch >= solo_dev->nr_chans);
+	solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), 1);
+	solo_dev->v4l2_enc[ch]->reset_gop = 1;
+}
+
+static int enc_gop_reset(struct solo6010_dev *solo_dev, u8 ch, u8 vop)
+{
+	BUG_ON(ch >= solo_dev->nr_chans);
+	if (!solo_dev->v4l2_enc[ch]->reset_gop)
+		return 0;
+	if (vop)
+		return 1;
+	solo_dev->v4l2_enc[ch]->reset_gop = 0;
+	solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch),
+		       solo_dev->v4l2_enc[ch]->gop);
+	return 0;
+}
+
+static int enc_get_mpeg_dma_t(struct solo6010_dev *solo_dev, dma_addr_t buf,
+			      unsigned int off, unsigned int size)
+{
+	int ret;
+
+	if (off > SOLO_MP4E_EXT_SIZE(solo_dev))
+		return -EINVAL;
+
+	if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev))
+		return solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf,
+				      SOLO_MP4E_EXT_ADDR(solo_dev) + off, size);
+
+	/* Buffer wrap */
+	ret = solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf,
+			    SOLO_MP4E_EXT_ADDR(solo_dev) + off,
+			    SOLO_MP4E_EXT_SIZE(solo_dev) - off);
+
+	ret |= solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0,
+			      buf + SOLO_MP4E_EXT_SIZE(solo_dev) - off,
+			      SOLO_MP4E_EXT_ADDR(solo_dev),
+			      size + off - SOLO_MP4E_EXT_SIZE(solo_dev));
+
+	return ret;
+}
+
+static int enc_get_mpeg_dma(struct solo6010_dev *solo_dev, void *buf,
+			    unsigned int off, unsigned int size)
+{
+	int ret;
+
+	dma_addr_t dma_addr = pci_map_single(solo_dev->pdev, buf, size,
+					     PCI_DMA_FROMDEVICE);
+	ret = enc_get_mpeg_dma_t(solo_dev, dma_addr, off, size);
+	pci_unmap_single(solo_dev->pdev, dma_addr, size, PCI_DMA_FROMDEVICE);
+
+	return ret;
+}
+
+static int enc_get_jpeg_dma(struct solo6010_dev *solo_dev, dma_addr_t buf,
+			    unsigned int off, unsigned int size)
+{
+	int ret;
+
+	if (off > SOLO_JPEG_EXT_SIZE(solo_dev))
+		return -EINVAL;
+
+	if (off + size <= SOLO_JPEG_EXT_SIZE(solo_dev))
+		return solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_JPEG, 0, buf,
+				      SOLO_JPEG_EXT_ADDR(solo_dev) + off, size);
+
+	/* Buffer wrap */
+	ret = solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_JPEG, 0, buf,
+			     SOLO_JPEG_EXT_ADDR(solo_dev) + off,
+			     SOLO_JPEG_EXT_SIZE(solo_dev) - off);
+
+	ret |= solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_JPEG, 0,
+			      buf + SOLO_JPEG_EXT_SIZE(solo_dev) - off,
+			      SOLO_JPEG_EXT_ADDR(solo_dev),
+			      size + off - SOLO_JPEG_EXT_SIZE(solo_dev));
+
+	return ret;
+}
+
+static int solo_fill_jpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf,
+			  struct videobuf_buffer *vb, dma_addr_t vbuf)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	u8 *p = videobuf_queue_to_vaddr(&fh->vidq, vb);
+
+	memcpy(p, jpeg_header, sizeof(jpeg_header));
+	p[SOF0_START + 5] = 0xff & (solo_enc->height >> 8);
+	p[SOF0_START + 6] = 0xff & solo_enc->height;
+	p[SOF0_START + 7] = 0xff & (solo_enc->width >> 8);
+	p[SOF0_START + 8] = 0xff & solo_enc->width;
+
+	vbuf += sizeof(jpeg_header);
+	vb->size = enc_buf->jpeg_size + sizeof(jpeg_header);
+
+	return enc_get_jpeg_dma(solo_dev, vbuf, enc_buf->jpeg_off,
+				enc_buf->jpeg_size);
+}
+
+static int solo_fill_mpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf,
+			  struct videobuf_buffer *vb, dma_addr_t vbuf)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct vop_header vh;
+	int ret;
+	int frame_size, frame_off;
+
+	if (WARN_ON_ONCE(enc_buf->size <= sizeof(vh)))
+		return -1;
+
+	/* First get the hardware vop header (not real mpeg) */
+	ret = enc_get_mpeg_dma(solo_dev, &vh, enc_buf->off, sizeof(vh));
+	if (ret)
+		return -1;
+
+	if (WARN_ON_ONCE(vh.size > enc_buf->size))
+		return -1;
+
+	vb->width = vh.hsize << 4;
+	vb->height = vh.vsize << 4;
+	vb->size = vh.size;
+
+	/* If this is a key frame, add extra m4v header */
+	if (!enc_buf->vop) {
+		u16 fps = solo_dev->fps * 1000;
+		u16 interval = solo_enc->interval * 1000;
+		u8 *p = videobuf_queue_to_vaddr(&fh->vidq, vb);
+
+		memcpy(p, vid_vop_header, sizeof(vid_vop_header));
+
+		if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+			p[10] |= ((XVID_PAR_43_NTSC << 3) & 0x78);
+		else
+			p[10] |= ((XVID_PAR_43_PAL << 3) & 0x78);
+
+		/* Frame rate and interval */
+		p[22] = fps >> 4;
+		p[23] = ((fps << 4) & 0xf0) | 0x0c | ((interval >> 13) & 0x3);
+		p[24] = (interval >> 5) & 0xff;
+		p[25] = ((interval << 3) & 0xf8) | 0x04;
+
+		/* Width and height */
+		p[26] = (vb->width >> 3) & 0xff;
+		p[27] = ((vb->height >> 9) & 0x0f) | 0x10;
+		p[28] = (vb->height >> 1) & 0xff;
+
+		/* Interlace */
+		if (vh.interlace)
+			p[29] |= 0x20;
+
+		/* Adjust the dma buffer past this header */
+		vb->size += sizeof(vid_vop_header);
+		vbuf += sizeof(vid_vop_header);
+	}
+
+	/* Now get the actual mpeg payload */
+	frame_off = (enc_buf->off + sizeof(vh)) % SOLO_MP4E_EXT_SIZE(solo_dev);
+	frame_size = enc_buf->size - sizeof(vh);
+	ret = enc_get_mpeg_dma_t(solo_dev, vbuf, frame_off, frame_size);
+	if (WARN_ON_ONCE(ret))
+		return -1;
+
+	return 0;
+}
+
+/* On successful return (0), leaves solo_enc->lock unlocked */
+static int solo_enc_fillbuf(struct solo_enc_fh *fh,
+			    struct videobuf_buffer *vb)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct solo_enc_buf *enc_buf = NULL;
+	dma_addr_t vbuf;
+	int ret;
+	u16 idx = fh->rd_idx;
+
+	while (idx != solo_dev->enc_wr_idx) {
+		struct solo_enc_buf *ebuf = &solo_dev->enc_buf[idx];
+		idx = (idx + 1) % SOLO_NR_RING_BUFS;
+		if (fh->fmt == V4L2_PIX_FMT_MPEG) {
+			if (fh->type != ebuf->type)
+				continue;
+			if (ebuf->ch == solo_enc->ch) {
+				enc_buf = ebuf;
+				break;
+			}
+		} else if (ebuf->ch == solo_enc->ch) {
+			/* For mjpeg, keep reading to the newest frame */
+			enc_buf = ebuf;
+		}
+	}
+
+	fh->rd_idx = idx;
+
+	if (!enc_buf)
+		return -1;
+
+	if ((fh->fmt == V4L2_PIX_FMT_MPEG &&
+	     vb->bsize < enc_buf->size) ||
+	    (fh->fmt == V4L2_PIX_FMT_MJPEG &&
+	     vb->bsize < (enc_buf->jpeg_size + sizeof(jpeg_header)))) {
+		return -1;
+	}
+
+	if (!(vbuf = videobuf_to_dma_contig(vb)))
+		return -1;
+
+	/* Is it ok that we mess with this buffer out of lock? */
+	spin_unlock(&solo_enc->lock);
+
+	if (fh->fmt == V4L2_PIX_FMT_MPEG)
+		ret = solo_fill_mpeg(fh, enc_buf, vb, vbuf);
+	else
+		ret = solo_fill_jpeg(fh, enc_buf, vb, vbuf);
+
+	if (ret) // Ignore failures
+		return 0;
+
+	list_del(&vb->queue);
+	vb->field_count++;
+	vb->ts = enc_buf->ts;
+	vb->state = VIDEOBUF_DONE;
+
+	wake_up(&vb->done);
+
+	return 0;
+}
+
+static void solo_enc_thread_try(struct solo_enc_fh *fh)
+{
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct videobuf_buffer *vb;
+
+	for (;;) {
+		spin_lock(&solo_enc->lock);
+
+		if (list_empty(&fh->vidq_active))
+			break;
+
+		vb = list_first_entry(&fh->vidq_active,
+				      struct videobuf_buffer, queue);
+
+		if (!waitqueue_active(&vb->done))
+			break;
+
+		/* On success, returns with solo_enc->lock unlocked */
+		if (solo_enc_fillbuf(fh, vb))
+			break;
+	}
+
+	assert_spin_locked(&solo_enc->lock);
+	spin_unlock(&solo_enc->lock);
+}
+
+static int solo_enc_thread(void *data)
+{
+	struct solo_enc_fh *fh = data;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	DECLARE_WAITQUEUE(wait, current);
+
+	set_freezable();
+	add_wait_queue(&solo_enc->thread_wait, &wait);
+
+	for (;;) {
+		long timeout = schedule_timeout_interruptible(HZ);
+		if (timeout == -ERESTARTSYS || kthread_should_stop())
+			break;
+		solo_enc_thread_try(fh);
+		try_to_freeze();
+	}
+
+	remove_wait_queue(&solo_enc->thread_wait, &wait);
+
+        return 0;
+}
+
+void solo_motion_isr(struct solo6010_dev *solo_dev)
+{
+	u32 status;
+	int i;
+
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_MOTION);
+
+	status = solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS);
+
+	for (i = 0; i < solo_dev->nr_chans; i++) {
+		struct solo_enc_dev *solo_enc = solo_dev->v4l2_enc[i];
+
+		BUG_ON(solo_enc == NULL);
+
+		if (solo_enc->motion_detected)
+			continue;
+		if (!(status & (1 << i)))
+			continue;
+
+		solo_enc->motion_detected = 1;
+	}
+}
+
+void solo_enc_v4l2_isr(struct solo6010_dev *solo_dev)
+{
+	struct solo_enc_buf *enc_buf;
+	struct videnc_status vstatus;
+	u32 mpeg_current, mpeg_next, mpeg_size;
+	u32 jpeg_current, jpeg_next, jpeg_size;
+	u32 reg_mpeg_size;
+	u8 cur_q, vop_type;
+	u8 ch;
+	enum solo_enc_types enc_type;
+
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_ENCODER);
+
+	vstatus.status11 = solo_reg_read(solo_dev, SOLO_VE_STATE(11));
+	cur_q = (vstatus.status11_st.last_queue + 1) % MP4_QS;
+
+	vstatus.status0 = solo_reg_read(solo_dev, SOLO_VE_STATE(0));
+	reg_mpeg_size = (vstatus.status0_st.mp4_enc_code_size + 64 + 32) &
+			(~31);
+
+	while (solo_dev->enc_idx != cur_q) {
+		mpeg_current = solo_reg_read(solo_dev,
+					SOLO_VE_MPEG4_QUE(solo_dev->enc_idx));
+		jpeg_current = solo_reg_read(solo_dev,
+					SOLO_VE_JPEG_QUE(solo_dev->enc_idx));
+		solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS;
+		mpeg_next = solo_reg_read(solo_dev,
+					SOLO_VE_MPEG4_QUE(solo_dev->enc_idx));
+		jpeg_next = solo_reg_read(solo_dev,
+					SOLO_VE_JPEG_QUE(solo_dev->enc_idx));
+
+		if ((ch = (mpeg_current >> 24) & 0x1f) >= SOLO_MAX_CHANNELS) {
+			ch -= SOLO_MAX_CHANNELS;
+			enc_type = SOLO_ENC_TYPE_EXT;
+		} else
+			enc_type = SOLO_ENC_TYPE_STD;
+
+		vop_type = (mpeg_current >> 29) & 3;
+
+		mpeg_current &= 0x00ffffff;
+		mpeg_next    &= 0x00ffffff;
+		jpeg_current &= 0x00ffffff;
+		jpeg_next    &= 0x00ffffff;
+
+		mpeg_size = (SOLO_MP4E_EXT_SIZE(solo_dev) +
+			     mpeg_next - mpeg_current) %
+			    SOLO_MP4E_EXT_SIZE(solo_dev);
+
+		jpeg_size = (SOLO_JPEG_EXT_SIZE(solo_dev) +
+			     jpeg_next - jpeg_current) %
+			    SOLO_JPEG_EXT_SIZE(solo_dev);
+
+		/* XXX I think this means we had a ring overflow? */
+		if (mpeg_current > mpeg_next && mpeg_size != reg_mpeg_size) {
+			enc_reset_gop(solo_dev, ch);
+			continue;
+		}
+
+		/* When resetting the GOP, skip frames until I-frame */
+		if (enc_gop_reset(solo_dev, ch, vop_type))
+			continue;
+
+		enc_buf = &solo_dev->enc_buf[solo_dev->enc_wr_idx];
+
+		enc_buf->vop = vop_type;
+		enc_buf->ch = ch;
+		enc_buf->off = mpeg_current;
+		enc_buf->size = mpeg_size;
+		enc_buf->jpeg_off = jpeg_current;
+		enc_buf->jpeg_size = jpeg_size;
+		enc_buf->type = enc_type;
+
+		do_gettimeofday(&enc_buf->ts);
+
+		solo_dev->enc_wr_idx = (solo_dev->enc_wr_idx + 1) %
+					SOLO_NR_RING_BUFS;
+
+		wake_up_interruptible(&solo_dev->v4l2_enc[ch]->thread_wait);
+	}
+
+	return;
+}
+
+static int solo_enc_buf_setup(struct videobuf_queue *vq, unsigned int *count,
+			      unsigned int *size)
+{
+        *size = FRAME_BUF_SIZE;
+
+        if (*count < MIN_VID_BUFFERS)
+		*count = MIN_VID_BUFFERS;
+
+        return 0;
+}
+
+static int solo_enc_buf_prepare(struct videobuf_queue *vq,
+				struct videobuf_buffer *vb,
+				enum v4l2_field field)
+{
+	struct solo_enc_fh *fh = vq->priv_data;
+	struct solo_enc_dev *solo_enc = fh->enc;
+
+	vb->size = FRAME_BUF_SIZE;
+	if (vb->baddr != 0 && vb->bsize < vb->size)
+		return -EINVAL;
+
+	/* These properties only change when queue is idle */
+	vb->width = solo_enc->width;
+	vb->height = solo_enc->height;
+	vb->field  = field;
+
+	if (vb->state == VIDEOBUF_NEEDS_INIT) {
+		int rc = videobuf_iolock(vq, vb, NULL);
+		if (rc < 0) {
+			videobuf_dma_contig_free(vq, vb);
+			vb->state = VIDEOBUF_NEEDS_INIT;
+			return rc;
+		}
+	}
+	vb->state = VIDEOBUF_PREPARED;
+
+	return 0;
+}
+
+static void solo_enc_buf_queue(struct videobuf_queue *vq,
+			       struct videobuf_buffer *vb)
+{
+	struct solo_enc_fh *fh = vq->priv_data;
+
+	vb->state = VIDEOBUF_QUEUED;
+	list_add_tail(&vb->queue, &fh->vidq_active);
+	wake_up_interruptible(&fh->enc->thread_wait);
+}
+
+static void solo_enc_buf_release(struct videobuf_queue *vq,
+				 struct videobuf_buffer *vb)
+{
+	videobuf_dma_contig_free(vq, vb);
+	vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+static struct videobuf_queue_ops solo_enc_video_qops = {
+	.buf_setup	= solo_enc_buf_setup,
+	.buf_prepare	= solo_enc_buf_prepare,
+	.buf_queue	= solo_enc_buf_queue,
+	.buf_release	= solo_enc_buf_release,
+};
+
+static unsigned int solo_enc_poll(struct file *file,
+				  struct poll_table_struct *wait)
+{
+	struct solo_enc_fh *fh = file->private_data;
+
+	return videobuf_poll_stream(file, &fh->vidq, wait);
+}
+
+static int solo_enc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct solo_enc_fh *fh = file->private_data;
+
+	return videobuf_mmap_mapper(&fh->vidq, vma);
+}
+
+static int solo_enc_open(struct file *file)
+{
+	struct solo_enc_dev *solo_enc = video_drvdata(file);
+	struct solo_enc_fh *fh;
+
+	if ((fh = kzalloc(sizeof(*fh), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	spin_lock(&solo_enc->lock);
+
+	fh->enc = solo_enc;
+	file->private_data = fh;
+	INIT_LIST_HEAD(&fh->vidq_active);
+	fh->fmt = V4L2_PIX_FMT_MPEG;
+	fh->type = SOLO_ENC_TYPE_STD;
+
+	videobuf_queue_dma_contig_init(&fh->vidq, &solo_enc_video_qops,
+				    &solo_enc->solo_dev->pdev->dev,
+				    &solo_enc->lock,
+				    V4L2_BUF_TYPE_VIDEO_CAPTURE,
+				    V4L2_FIELD_INTERLACED,
+				    sizeof(struct videobuf_buffer), fh);
+
+	spin_unlock(&solo_enc->lock);
+
+	return 0;
+}
+
+static ssize_t solo_enc_read(struct file *file, char __user *data,
+			     size_t count, loff_t *ppos)
+{
+	struct solo_enc_fh *fh = file->private_data;
+	struct solo_enc_dev *solo_enc = fh->enc;
+
+	/* Make sure the encoder is on */
+	if (!fh->enc_on) {
+		int ret;
+
+		spin_lock(&solo_enc->lock);
+		ret = solo_enc_on(fh);
+	        spin_unlock(&solo_enc->lock);
+		if (ret)
+			return ret;
+	}
+
+	return videobuf_read_stream(&fh->vidq, data, count, ppos, 0,
+				    file->f_flags & O_NONBLOCK);
+}
+
+static int solo_enc_release(struct file *file)
+{
+	struct solo_enc_fh *fh = file->private_data;
+
+	videobuf_stop(&fh->vidq);
+	videobuf_mmap_free(&fh->vidq);
+	solo_enc_off(fh);
+	kfree(fh);
+
+	return 0;
+}
+
+static int solo_enc_querycap(struct file *file, void  *priv,
+			     struct v4l2_capability *cap)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	strcpy(cap->driver, SOLO6010_NAME);
+	snprintf(cap->card, sizeof(cap->card), "Softlogic 6010 Enc %d",
+		 solo_enc->ch);
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI %s",
+		 pci_name(solo_dev->pdev));
+	cap->version = SOLO6010_VER_NUM;
+	cap->capabilities =     V4L2_CAP_VIDEO_CAPTURE |
+				V4L2_CAP_READWRITE |
+				V4L2_CAP_STREAMING;
+	return 0;
+}
+
+static int solo_enc_enum_input(struct file *file, void *priv,
+			       struct v4l2_input *input)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_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;
+
+	if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+		input->std = V4L2_STD_NTSC_M;
+	else
+		input->std = V4L2_STD_PAL_M;
+
+	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)
+{
+	switch (f->index) {
+	case 0:
+		f->pixelformat = V4L2_PIX_FMT_MPEG;
+		strcpy(f->description, "MPEG-4 AVC");
+		break;
+	case 1:
+		f->pixelformat = V4L2_PIX_FMT_MJPEG;
+		strcpy(f->description, "MJPEG");
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	f->flags = V4L2_FMT_FLAG_COMPRESSED;
+
+	return 0;
+}
+
+static int solo_enc_try_fmt_cap(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+
+	if (pix->pixelformat != V4L2_PIX_FMT_MPEG &&
+	    pix->pixelformat != V4L2_PIX_FMT_MJPEG)
+		return -EINVAL;
+
+	/* We cannot change width/height in mid read */
+	if (atomic_read(&solo_enc->readers) > 0) {
+		if (pix->width != solo_enc->width ||
+		    pix->height != solo_enc->height)
+			return -EBUSY;
+	} else if (!(pix->width == solo_dev->video_hsize &&
+	      pix->height == solo_dev->video_vsize << 1) &&
+	    !(pix->width == solo_dev->video_hsize >> 1 &&
+	      pix->height == solo_dev->video_vsize)) {
+		/* Default to CIF 1/2 size */
+		pix->width = solo_dev->video_hsize >> 1;
+		pix->height = solo_dev->video_vsize;
+	}
+
+	if (pix->field == V4L2_FIELD_ANY)
+		pix->field = V4L2_FIELD_INTERLACED;
+	else if (pix->field != V4L2_FIELD_INTERLACED) {
+		pix->field = V4L2_FIELD_INTERLACED;
+	}
+
+	/* Just set these */
+	pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+	pix->sizeimage = FRAME_BUF_SIZE;
+
+	return 0;
+}
+
+static int solo_enc_set_fmt_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	int ret;
+
+	spin_lock(&solo_enc->lock);
+
+	if ((ret = solo_enc_try_fmt_cap(file, priv, f))) {
+		spin_unlock(&solo_enc->lock);
+		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 */
+	fh->fmt = pix->pixelformat;
+
+	if (pix->priv)
+		fh->type = SOLO_ENC_TYPE_EXT;
+	ret = solo_enc_on(fh);
+
+	spin_unlock(&solo_enc->lock);
+
+	return ret;
+}
+
+static int solo_enc_get_fmt_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+
+	pix->width = solo_enc->width;
+	pix->height = solo_enc->height;
+	pix->pixelformat = fh->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_reqbufs(struct file *file, void *priv, 
+			    struct v4l2_requestbuffers *req)
+{
+	struct solo_enc_fh *fh = priv;
+
+	return videobuf_reqbufs(&fh->vidq, req);
+}
+
+static int solo_enc_querybuf(struct file *file, void *priv,
+			     struct v4l2_buffer *buf)
+{
+	struct solo_enc_fh *fh = priv;
+
+	return videobuf_querybuf(&fh->vidq, buf);
+}
+
+static int solo_enc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+	struct solo_enc_fh *fh = priv;
+
+	return videobuf_qbuf(&fh->vidq, buf);
+}
+
+static int solo_enc_dqbuf(struct file *file, void *priv,
+			  struct v4l2_buffer *buf)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	int ret;
+
+	/* Make sure the encoder is on */
+	if (!fh->enc_on) {
+		spin_lock(&solo_enc->lock);
+		ret = solo_enc_on(fh);
+		spin_unlock(&solo_enc->lock);
+		if (ret)
+			return ret;
+	}
+
+	ret = videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK);
+	if (ret)
+		return ret;
+
+	/* Signal motion detection */
+	if (solo_is_motion_on(solo_enc)) {
+		buf->flags |= V4L2_BUF_FLAG_MOTION_ON;
+		if (solo_enc->motion_detected) {
+			buf->flags |= V4L2_BUF_FLAG_MOTION_DETECTED;
+			solo_reg_write(solo_enc->solo_dev, SOLO_VI_MOT_CLEAR,
+				       1 << solo_enc->ch);
+			solo_enc->motion_detected = 0;
+		}
+	}
+
+	/* Check for key frame on mpeg data */
+	if (fh->fmt == V4L2_PIX_FMT_MPEG) {
+		struct videobuf_buffer *vb = fh->vidq.bufs[buf->index];
+		u8 *p = videobuf_queue_to_vaddr(&fh->vidq, vb);
+		if (p[3] == 0x00)
+			buf->flags |= V4L2_BUF_FLAG_KEYFRAME;
+		else
+			buf->flags |= V4L2_BUF_FLAG_PFRAME;
+	}
+
+	return 0;
+}
+
+static int solo_enc_streamon(struct file *file, void *priv,
+			     enum v4l2_buf_type i)
+{
+	struct solo_enc_fh *fh = priv;
+
+	if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return videobuf_streamon(&fh->vidq);
+}
+
+static int solo_enc_streamoff(struct file *file, void *priv,
+			      enum v4l2_buf_type i)
+{
+	struct solo_enc_fh *fh = priv;
+
+	if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return videobuf_streamoff(&fh->vidq);
+}
+
+static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+	return 0;
+}
+
+static int solo_enum_framesizes(struct file *file, void *priv,
+				struct v4l2_frmsizeenum *fsize)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo6010_dev *solo_dev = fh->enc->solo_dev;
+
+	if (fsize->pixel_format != V4L2_PIX_FMT_MPEG)
+		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_fh *fh = priv;
+	struct solo6010_dev *solo_dev = fh->enc->solo_dev;
+
+	if (fintv->pixel_format != V4L2_PIX_FMT_MPEG || fintv->index)
+		return -EINVAL;
+
+	fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+
+	fintv->stepwise.min.numerator = solo_dev->fps;
+	fintv->stepwise.min.denominator = 1;
+
+	fintv->stepwise.max.numerator = solo_dev->fps;
+	fintv->stepwise.max.denominator = 15;
+
+	fintv->stepwise.step.numerator = 1;
+	fintv->stepwise.step.denominator = 1;
+
+	return 0;
+}
+
+static int solo_g_parm(struct file *file, void *priv,
+		       struct v4l2_streamparm *sp)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct v4l2_captureparm *cp = &sp->parm.capture;
+
+	cp->capability = V4L2_CAP_TIMEPERFRAME;
+	cp->timeperframe.numerator = solo_enc->interval;
+	cp->timeperframe.denominator = solo_dev->fps;
+	cp->capturemode = 0;
+	/* XXX: Shouldn't we be able to get/set this from videobuf? */
+	cp->readbuffers = 2;
+
+        return 0;
+}
+
+static int solo_s_parm(struct file *file, void *priv,
+		       struct v4l2_streamparm *sp)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+	struct v4l2_captureparm *cp = &sp->parm.capture;
+
+	spin_lock(&solo_enc->lock);
+
+	if (atomic_read(&solo_enc->readers) > 0) {
+		spin_unlock(&solo_enc->lock);
+		return -EBUSY;
+	}
+
+	if ((cp->timeperframe.numerator == 0) ||
+	    (cp->timeperframe.denominator == 0)) {
+		/* reset framerate */
+		cp->timeperframe.numerator = 1;
+		cp->timeperframe.denominator = solo_dev->fps;
+	}
+
+	if (cp->timeperframe.denominator != solo_dev->fps)
+		cp->timeperframe.denominator = solo_dev->fps;
+
+	if (cp->timeperframe.numerator > 15)
+		cp->timeperframe.numerator = 15;
+
+	solo_enc->interval = cp->timeperframe.numerator;
+
+	cp->capability = V4L2_CAP_TIMEPERFRAME;
+
+	solo_enc->gop = max(solo_dev->fps / solo_enc->interval, 1);
+	solo_update_mode(solo_enc);
+
+	spin_unlock(&solo_enc->lock);
+
+        return 0;
+}
+
+static int solo_queryctrl(struct file *file, void *priv,
+			  struct v4l2_queryctrl *qc)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id);
+	if (!qc->id)
+		return -EINVAL;
+
+	switch (qc->id) {
+	case V4L2_CID_BRIGHTNESS:
+	case V4L2_CID_CONTRAST:
+	case V4L2_CID_SATURATION:
+	case V4L2_CID_HUE:
+		return v4l2_ctrl_query_fill(qc, 0x00, 0xff, 1, 0x80);
+	case V4L2_CID_SHARPNESS:
+		return v4l2_ctrl_query_fill(qc, 0x00, 0x0f, 1, 0x00);
+	case V4L2_CID_MPEG_VIDEO_ENCODING:
+		return v4l2_ctrl_query_fill(
+			qc, V4L2_MPEG_VIDEO_ENCODING_MPEG_1,
+			V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, 1,
+			V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC);
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		return v4l2_ctrl_query_fill(qc, 1, 255, 1, solo_dev->fps);
+#ifdef PRIVATE_CIDS
+	case V4L2_CID_MOTION_THRESHOLD:
+		qc->flags |= V4L2_CTRL_FLAG_SLIDER;
+		qc->type = V4L2_CTRL_TYPE_INTEGER;
+		qc->minimum = 0;
+		qc->maximum = 0xffff;
+		qc->step = 1;
+		qc->default_value = SOLO_DEF_MOT_THRESH;
+		strlcpy(qc->name, "Motion Detection Threshold",
+			sizeof(qc->name));
+		return 0;
+	case V4L2_CID_MOTION_ENABLE:
+		qc->type = V4L2_CTRL_TYPE_BOOLEAN;
+		qc->minimum = 0;
+		qc->maximum = qc->step = 1;
+		qc->default_value = 0;
+		strlcpy(qc->name, "Motion Detection Enable", sizeof(qc->name));
+		return 0;
+#else
+	case V4L2_CID_MOTION_THRESHOLD:
+		return v4l2_ctrl_query_fill(qc, 0, 0xffff, 1,
+					    SOLO_DEF_MOT_THRESH);
+	case V4L2_CID_MOTION_ENABLE:
+		return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0);
+#endif
+	case V4L2_CID_RDS_TX_RADIO_TEXT:
+		qc->type = V4L2_CTRL_TYPE_STRING;
+		qc->minimum = 0;
+		qc->maximum = OSD_TEXT_MAX;
+		qc->step = 1;
+		qc->default_value = 0;
+		strlcpy(qc->name, "OSD Text", sizeof(qc->name));
+		return 0;
+	}
+
+        return -EINVAL;
+}
+
+static int solo_querymenu(struct file *file, void *priv,
+			  struct v4l2_querymenu *qmenu)
+{
+	struct v4l2_queryctrl qctrl;
+	int err;
+
+	qctrl.id = qmenu->id;
+	if ((err = solo_queryctrl(file, priv, &qctrl)))
+		return err;
+
+	return v4l2_ctrl_query_menu(qmenu, &qctrl, NULL);
+}
+
+static int solo_g_ctrl(struct file *file, void *priv,
+		       struct v4l2_control *ctrl)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	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_get_ctrl_val(solo_dev, ctrl->id, solo_enc->ch,
+					 &ctrl->value);
+	case V4L2_CID_MPEG_VIDEO_ENCODING:
+		ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC;
+		break;
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		ctrl->value = solo_enc->gop;
+		break;
+	case V4L2_CID_MOTION_THRESHOLD:
+		ctrl->value = solo_enc->motion_thresh;
+		break;
+	case V4L2_CID_MOTION_ENABLE:
+		ctrl->value = solo_is_motion_on(solo_enc);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int solo_s_ctrl(struct file *file, void *priv,
+		       struct v4l2_control *ctrl)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	struct solo6010_dev *solo_dev = solo_enc->solo_dev;
+
+	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->value);
+	case V4L2_CID_MPEG_VIDEO_ENCODING:
+		if (ctrl->value != V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC)
+			return -ERANGE;
+		break;
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		if (ctrl->value < 1 || ctrl->value > 255)
+			return -ERANGE;
+		solo_enc->gop = ctrl->value;
+		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);
+		break;
+	case V4L2_CID_MOTION_THRESHOLD:
+		/* TODO accept value on lower 16-bits and use high
+		 * 16-bits to assign the value to a specific block */
+		if (ctrl->value < 0 || ctrl->value > 0xffff)
+			return -ERANGE;
+		solo_enc->motion_thresh = ctrl->value;
+		solo_set_motion_threshold(solo_dev, solo_enc->ch, ctrl->value);
+		break;
+	case V4L2_CID_MOTION_ENABLE:
+		solo_motion_toggle(solo_enc, ctrl->value);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int solo_s_ext_ctrls(struct file *file, void *priv,
+			    struct v4l2_ext_controls *ctrls)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	int i;
+
+	for (i = 0; i < ctrls->count; i++) {
+		struct v4l2_ext_control *ctrl = (ctrls->controls + i);
+		int err;
+
+		switch (ctrl->id) {
+		case V4L2_CID_RDS_TX_RADIO_TEXT:
+			if (ctrl->size - 1 > OSD_TEXT_MAX)
+                                err = -ERANGE;
+			else {
+                        	err = copy_from_user(solo_enc->osd_text,
+						     ctrl->string,
+						     OSD_TEXT_MAX);
+				solo_enc->osd_text[OSD_TEXT_MAX] = '\0';
+				if (!err)
+					err = solo_osd_print(solo_enc);
+			}
+			break;
+		default:
+			err = -EINVAL;
+		}
+
+		if (err < 0) {
+			ctrls->error_idx = i;
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int solo_g_ext_ctrls(struct file *file, void *priv,
+			    struct v4l2_ext_controls *ctrls)
+{
+	struct solo_enc_fh *fh = priv;
+	struct solo_enc_dev *solo_enc = fh->enc;
+	int i;
+
+	for (i = 0; i < ctrls->count; i++) {
+		struct v4l2_ext_control *ctrl = (ctrls->controls + i);
+		int err;
+
+		switch (ctrl->id) {
+		case V4L2_CID_RDS_TX_RADIO_TEXT:
+			if (ctrl->size < OSD_TEXT_MAX) {
+				ctrl->size = OSD_TEXT_MAX;
+				err = -ENOSPC;
+			} else {
+				err = copy_to_user(ctrl->string,
+						   solo_enc->osd_text,
+						   OSD_TEXT_MAX);
+			}
+			break;
+		default:
+			err = -EINVAL;
+		}
+
+		if (err < 0) {
+			ctrls->error_idx = i;
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static const struct v4l2_file_operations solo_enc_fops = {
+	.owner			= THIS_MODULE,
+	.open			= solo_enc_open,
+	.release		= solo_enc_release,
+	.read			= solo_enc_read,
+	.poll			= solo_enc_poll,
+	.mmap			= solo_enc_mmap,
+	.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,
+	/* 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			= solo_enc_reqbufs,
+	.vidioc_querybuf		= solo_enc_querybuf,
+	.vidioc_qbuf			= solo_enc_qbuf,
+	.vidioc_dqbuf			= solo_enc_dqbuf,
+	.vidioc_streamon		= solo_enc_streamon,
+	.vidioc_streamoff		= solo_enc_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,
+	/* Controls */
+	.vidioc_queryctrl		= solo_queryctrl,
+	.vidioc_querymenu		= solo_querymenu,
+	.vidioc_g_ctrl			= solo_g_ctrl,
+	.vidioc_s_ctrl			= solo_s_ctrl,
+	.vidioc_g_ext_ctrls		= solo_g_ext_ctrls,
+	.vidioc_s_ext_ctrls		= solo_s_ext_ctrls,
+};
+
+static struct video_device solo_enc_template = {
+	.name			= SOLO6010_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_M,
+	.current_norm		= V4L2_STD_NTSC_M,
+};
+
+static struct solo_enc_dev *solo_enc_alloc(struct solo6010_dev *solo_dev, u8 ch)
+{
+	struct solo_enc_dev *solo_enc;
+	int ret;
+
+	solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL);
+	if (!solo_enc)
+		return ERR_PTR(-ENOMEM);
+
+	solo_enc->vfd = video_device_alloc();
+	if (!solo_enc->vfd) {
+		kfree(solo_enc);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	solo_enc->solo_dev = solo_dev;
+	solo_enc->ch = ch;
+
+	*solo_enc->vfd = solo_enc_template;
+	solo_enc->vfd->parent = &solo_dev->pdev->dev;
+	ret = video_register_device(solo_enc->vfd, VFL_TYPE_GRABBER,
+				    video_nr);
+	if (ret < 0) {
+		video_device_release(solo_enc->vfd);
+		kfree(solo_enc);
+		return ERR_PTR(ret);
+	}
+
+	video_set_drvdata(solo_enc->vfd, solo_enc);
+
+	snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name),
+		 "%s-enc (%i/%i)", SOLO6010_NAME, solo_dev->vfd->num,
+		 solo_enc->vfd->num);
+
+	if (video_nr >= 0)
+		video_nr++;
+
+	spin_lock_init(&solo_enc->lock);
+	init_waitqueue_head(&solo_enc->thread_wait);
+	atomic_set(&solo_enc->readers, 0);
+
+	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_thresh = SOLO_DEF_MOT_THRESH;
+
+	spin_lock(&solo_enc->lock);
+	solo_update_mode(solo_enc);
+	spin_unlock(&solo_enc->lock);
+
+	return solo_enc;
+}
+
+static void solo_enc_free(struct solo_enc_dev *solo_enc)
+{
+	if (solo_enc == NULL)
+		return;
+
+	video_unregister_device(solo_enc->vfd);
+	kfree(solo_enc);
+}
+
+int solo_enc_v4l2_init(struct solo6010_dev *solo_dev)
+{
+	int i;
+
+	for (i = 0; i < solo_dev->nr_chans; i++) {
+		solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i);
+		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]);
+		return ret;
+	}
+
+	/* D1 at MAX-FPS * 4 */
+	solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4;
+
+	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 0;
+}
+
+void solo_enc_v4l2_exit(struct solo6010_dev *solo_dev)
+{
+	int i;
+
+	solo6010_irq_off(solo_dev, SOLO_IRQ_MOTION);
+
+	for (i = 0; i < solo_dev->nr_chans; i++)
+		solo_enc_free(solo_dev->v4l2_enc[i]);
+}
diff --git a/drivers/staging/solo6x10/solo6010-v4l2.c b/drivers/staging/solo6x10/solo6010-v4l2.c
new file mode 100644
index 0000000..9537cc6
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010-v4l2.c
@@ -0,0 +1,859 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#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/videobuf-dma-contig.h>
+
+#include "solo6010.h"
+#include "solo6010-tw28.h"
+
+#define SOLO_HW_BPL		2048
+#define SOLO_DISP_PIX_FIELD	V4L2_FIELD_INTERLACED
+#define SOLO_DISP_BUF_SIZE	(64 * 1024) // 64k
+
+/* Image size is two fields, SOLO_HW_BPL is one horizontal line */
+#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		4
+
+/* Simple file handle */
+struct solo_filehandle {
+	struct solo6010_dev	*solo_dev;
+	struct videobuf_queue	vidq;
+	struct task_struct      *kthread;
+	spinlock_t		slock;
+	int			old_write;
+	struct list_head	vidq_active;
+};
+
+unsigned video_nr = -1;
+module_param(video_nr, uint, 0644);
+MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)");
+
+static void erase_on(struct solo6010_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 int erase_off(struct solo6010_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 solo6010_dev *solo_dev)
+{
+	solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_VIDEO_IN);
+	wake_up_interruptible(&solo_dev->disp_thread_wait);
+}
+
+static void solo_win_setup(struct solo6010_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 solo6010_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 solo6010_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 solo6010_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 solo6010_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_filehandle *fh,
+			 struct videobuf_buffer *vb)
+{
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+	dma_addr_t vbuf;
+	unsigned int fdma_addr;
+	int frame_size;
+	int error = 1;
+	int i;
+
+	if (!(vbuf = videobuf_to_dma_contig(vb)))
+		goto finish_buf;
+
+	if (erase_off(solo_dev)) {
+		void *p = videobuf_queue_to_vaddr(&fh->vidq, vb);
+		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;
+		goto finish_buf;
+	}
+
+	frame_size = SOLO_HW_BPL * solo_vlines(solo_dev);
+	fdma_addr = SOLO_DISP_EXT_ADDR(solo_dev) + (fh->old_write * frame_size);
+
+	for (i = 0; i < frame_size / SOLO_DISP_BUF_SIZE; i++) {
+		int j;
+		for (j = 0; j < (SOLO_DISP_BUF_SIZE / SOLO_HW_BPL); j++) {
+			if (solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_DISP, 0,
+					   vbuf, fdma_addr + (j * SOLO_HW_BPL),
+					   solo_bytesperline(solo_dev)))
+				goto finish_buf;
+			vbuf += solo_bytesperline(solo_dev);
+		}
+		fdma_addr += SOLO_DISP_BUF_SIZE;
+	}
+	error = 0;
+
+finish_buf:
+	if (error) {
+		vb->state = VIDEOBUF_ERROR;
+	} else {
+		vb->state = VIDEOBUF_DONE;
+		vb->field_count++;
+		do_gettimeofday(&vb->ts);
+	}
+
+	wake_up(&vb->done);
+
+	return;
+}
+
+static void solo_thread_try(struct solo_filehandle *fh)
+{
+	struct videobuf_buffer *vb;
+	unsigned int cur_write;
+
+	for (;;) {
+		spin_lock(&fh->slock);
+
+		if (list_empty(&fh->vidq_active))
+			break;
+
+		vb = list_first_entry(&fh->vidq_active, struct videobuf_buffer,
+				      queue);
+
+		if (!waitqueue_active(&vb->done))
+			break;
+
+		cur_write = SOLO_VI_STATUS0_PAGE(solo_reg_read(fh->solo_dev,
+							SOLO_VI_STATUS0));
+		if (cur_write == fh->old_write)
+			break;
+
+		fh->old_write = cur_write;
+		list_del(&vb->queue);
+
+		spin_unlock(&fh->slock);
+
+		solo_fillbuf(fh, vb);
+	}
+
+	assert_spin_locked(&fh->slock);
+	spin_unlock(&fh->slock);
+}
+
+static int solo_thread(void *data)
+{
+	struct solo_filehandle *fh = data;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+	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(fh);
+		try_to_freeze();
+	}
+
+	remove_wait_queue(&solo_dev->disp_thread_wait, &wait);
+
+        return 0;
+}
+
+static int solo_start_thread(struct solo_filehandle *fh)
+{
+	fh->kthread = kthread_run(solo_thread, fh, SOLO6010_NAME "_disp");
+
+	if (IS_ERR(fh->kthread))
+		return PTR_ERR(fh->kthread);
+
+	return 0;
+}
+
+static void solo_stop_thread(struct solo_filehandle *fh)
+{
+	if (fh->kthread) {
+		kthread_stop(fh->kthread);
+		fh->kthread = NULL;
+	}
+}
+
+static int solo_buf_setup(struct videobuf_queue *vq, unsigned int *count,
+			  unsigned int *size)
+{
+	struct solo_filehandle *fh = vq->priv_data;
+	struct solo6010_dev *solo_dev  = fh->solo_dev;
+
+        *size = solo_image_size(solo_dev);
+
+        if (*count < MIN_VID_BUFFERS)
+		*count = MIN_VID_BUFFERS;
+
+        return 0;
+}
+
+static int solo_buf_prepare(struct videobuf_queue *vq,
+			    struct videobuf_buffer *vb, enum v4l2_field field)
+{
+	struct solo_filehandle *fh  = vq->priv_data;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	vb->size = solo_image_size(solo_dev);
+	if (vb->baddr != 0 && vb->bsize < vb->size)
+		return -EINVAL;
+
+	/* XXX: These properties only change when queue is idle */
+	vb->width  = solo_dev->video_hsize;
+	vb->height = solo_vlines(solo_dev);
+	vb->bytesperline = solo_bytesperline(solo_dev);
+	vb->field  = field;
+
+	if (vb->state == VIDEOBUF_NEEDS_INIT) {
+		int rc = videobuf_iolock(vq, vb, NULL);
+		if (rc < 0) {
+			videobuf_dma_contig_free(vq, vb);
+			vb->state = VIDEOBUF_NEEDS_INIT;
+			return rc;
+		}
+	}
+	vb->state = VIDEOBUF_PREPARED;
+
+	return 0;
+}
+
+static void solo_buf_queue(struct videobuf_queue *vq,
+			   struct videobuf_buffer *vb)
+{
+	struct solo_filehandle *fh = vq->priv_data;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	vb->state = VIDEOBUF_QUEUED;
+	list_add_tail(&vb->queue, &fh->vidq_active);
+	wake_up_interruptible(&solo_dev->disp_thread_wait);
+}
+
+static void solo_buf_release(struct videobuf_queue *vq,
+			     struct videobuf_buffer *vb)
+{
+	videobuf_dma_contig_free(vq, vb);
+	vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+static struct videobuf_queue_ops solo_video_qops = {
+	.buf_setup	= solo_buf_setup,
+	.buf_prepare	= solo_buf_prepare,
+	.buf_queue	= solo_buf_queue,
+	.buf_release	= solo_buf_release,
+};
+
+static unsigned int solo_v4l2_poll(struct file *file,
+				   struct poll_table_struct *wait)
+{
+	struct solo_filehandle *fh = file->private_data;
+
+        return videobuf_poll_stream(file, &fh->vidq, wait);
+}
+
+static int solo_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct solo_filehandle *fh = file->private_data;
+
+	return videobuf_mmap_mapper(&fh->vidq, vma);
+}
+
+static int solo_v4l2_open(struct file *file)
+{
+	struct solo6010_dev *solo_dev = video_drvdata(file);
+	struct solo_filehandle *fh;
+	int ret;
+
+	if ((fh = kzalloc(sizeof(*fh), GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	spin_lock_init(&fh->slock);
+	INIT_LIST_HEAD(&fh->vidq_active);
+	fh->solo_dev = solo_dev;
+	file->private_data = fh;
+
+	if ((ret = solo_start_thread(fh))) {
+		kfree(fh);
+		return ret;
+	}
+
+	videobuf_queue_dma_contig_init(&fh->vidq, &solo_video_qops,
+				    &solo_dev->pdev->dev, &fh->slock,
+				    V4L2_BUF_TYPE_VIDEO_CAPTURE,
+				    SOLO_DISP_PIX_FIELD,
+				    sizeof(struct videobuf_buffer), fh);
+
+	return 0;
+}
+
+static ssize_t solo_v4l2_read(struct file *file, char __user *data,
+			      size_t count, loff_t *ppos)
+{
+	struct solo_filehandle *fh = file->private_data;
+
+	return videobuf_read_stream(&fh->vidq, data, count, ppos, 0,
+				    file->f_flags & O_NONBLOCK);
+}
+
+static int solo_v4l2_release(struct file *file)
+{
+	struct solo_filehandle *fh = file->private_data;
+
+	videobuf_stop(&fh->vidq);
+	videobuf_mmap_free(&fh->vidq);
+	solo_stop_thread(fh);
+	kfree(fh);
+
+	return 0;
+}
+
+static int solo_querycap(struct file *file, void  *priv,
+			 struct v4l2_capability *cap)
+{
+	struct solo_filehandle  *fh  = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	strcpy(cap->driver, SOLO6010_NAME);
+	strcpy(cap->card, "Softlogic 6010");
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI %s",
+		 pci_name(solo_dev->pdev));
+	cap->version = SOLO6010_VER_NUM;
+	cap->capabilities =     V4L2_CAP_VIDEO_CAPTURE |
+				V4L2_CAP_READWRITE |
+				V4L2_CAP_STREAMING;
+	return 0;
+}
+
+static int solo_enum_ext_input(struct solo6010_dev *solo_dev,
+			       struct v4l2_input *input)
+{
+	static const char *dispnames_1[] = { "4UP" };
+	static const char *dispnames_2[] = { "4UP-1", "4UP-2" };
+	static const char *dispnames_5[] = {
+		"4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP"
+	};
+	const char **dispnames;
+
+	if (input->index >= (solo_dev->nr_chans + solo_dev->nr_ext))
+		return -EINVAL;
+
+	if (solo_dev->nr_ext == 5)
+		dispnames = dispnames_5;
+	else if (solo_dev->nr_ext == 2)
+		dispnames = dispnames_2;
+	else
+		dispnames = dispnames_1;
+
+	snprintf(input->name, sizeof(input->name), "Multi %s",
+		 dispnames[input->index - solo_dev->nr_chans]);
+
+	return 0;
+}
+
+static int solo_enum_input(struct file *file, void *priv,
+			   struct v4l2_input *input)
+{
+	struct solo_filehandle *fh  = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	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;
+
+	if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
+		input->std = V4L2_STD_NTSC_M;
+	else
+		input->std = V4L2_STD_PAL_M;
+
+	return 0;
+}
+
+static int solo_set_input(struct file *file, void *priv, unsigned int index)
+{
+	struct solo_filehandle *fh = priv;
+
+	return solo_v4l2_set_ch(fh->solo_dev, index);
+}
+
+static int solo_get_input(struct file *file, void *priv, unsigned int *index)
+{
+	struct solo_filehandle *fh = priv;
+
+	*index = fh->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;
+	strlcpy(f->description, "UYUV 4:2:2 Packed", sizeof(f->description));
+
+	return 0;
+}
+
+static int solo_try_fmt_cap(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct solo_filehandle *fh = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	int image_size = solo_image_size(solo_dev);
+
+	/* Check supported sizes */
+	if (pix->width != solo_dev->video_hsize)
+		pix->width = solo_dev->video_hsize;
+	if (pix->height != solo_vlines(solo_dev))
+		pix->height = solo_vlines(solo_dev);
+	if (pix->sizeimage != image_size)
+		pix->sizeimage = image_size;
+
+	/* Check formats */
+	if (pix->field == V4L2_FIELD_ANY)
+		pix->field = SOLO_DISP_PIX_FIELD;
+
+	if (pix->pixelformat != V4L2_PIX_FMT_UYVY ||
+	    pix->field       != SOLO_DISP_PIX_FIELD ||
+	    pix->colorspace  != V4L2_COLORSPACE_SMPTE170M)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int solo_set_fmt_cap(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct solo_filehandle *fh = priv;
+
+	if (videobuf_queue_is_busy(&fh->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_filehandle *fh = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+	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 = SOLO_DISP_PIX_FIELD;
+	pix->sizeimage = solo_image_size(solo_dev);
+	pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
+	pix->bytesperline = solo_bytesperline(solo_dev);
+
+	return 0;
+}
+
+static int solo_reqbufs(struct file *file, void *priv, 
+			struct v4l2_requestbuffers *req)
+{
+	struct solo_filehandle *fh = priv;
+
+	return videobuf_reqbufs(&fh->vidq, req);
+}
+
+static int solo_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+	struct solo_filehandle *fh = priv;
+
+	return videobuf_querybuf(&fh->vidq, buf);
+}
+
+static int solo_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+	struct solo_filehandle *fh = priv;
+
+	return videobuf_qbuf(&fh->vidq, buf);
+}
+
+static int solo_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
+{
+	struct solo_filehandle *fh = priv;
+
+	return videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK);
+}
+
+static int solo_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct solo_filehandle *fh = priv;
+
+	if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return videobuf_streamon(&fh->vidq);
+}
+
+static int solo_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct solo_filehandle *fh = priv;
+
+	if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return videobuf_streamoff(&fh->vidq);
+}
+
+static int solo_s_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+	return 0;
+}
+
+static const u32 solo_motion_ctrls[] = {
+	V4L2_CID_MOTION_TRACE,
+	0
+};
+
+static const u32 *solo_ctrl_classes[] = {
+	solo_motion_ctrls,
+	NULL
+};
+
+static int solo_disp_queryctrl(struct file *file, void *priv,
+			       struct v4l2_queryctrl *qc)
+{
+	qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id);
+	if (!qc->id)
+		return -EINVAL;
+
+	switch (qc->id) {
+#ifdef PRIVATE_CIDS
+	case V4L2_CID_MOTION_TRACE:
+		qc->type = V4L2_CTRL_TYPE_BOOLEAN;
+		qc->minimum = 0;
+		qc->maximum = qc->step = 1;
+		qc->default_value = 0;
+		strlcpy(qc->name, "Motion Detection Trace", sizeof(qc->name));
+		return 0;
+#else
+	case V4L2_CID_MOTION_TRACE:
+		return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0);
+#endif
+	}
+	return -EINVAL;
+}
+
+static int solo_disp_g_ctrl(struct file *file, void *priv,
+			    struct v4l2_control *ctrl)
+{
+	struct solo_filehandle *fh = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MOTION_TRACE:
+		ctrl->value = solo_reg_read(solo_dev, SOLO_VI_MOTION_BAR)
+			? 1 : 0;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int solo_disp_s_ctrl(struct file *file, void *priv,
+			    struct v4l2_control *ctrl)
+{
+	struct solo_filehandle *fh = priv;
+	struct solo6010_dev *solo_dev = fh->solo_dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MOTION_TRACE:
+		if (ctrl->value) {
+			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;
+	}
+	return -EINVAL;
+}
+
+static const struct v4l2_file_operations solo_v4l2_fops = {
+	.owner			= THIS_MODULE,
+	.open			= solo_v4l2_open,
+	.release		= solo_v4l2_release,
+	.read			= solo_v4l2_read,
+	.poll			= solo_v4l2_poll,
+	.mmap			= solo_v4l2_mmap,
+	.ioctl			= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = {
+	.vidioc_querycap		= solo_querycap,
+	.vidioc_s_std			= solo_s_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			= solo_reqbufs,
+	.vidioc_querybuf		= solo_querybuf,
+	.vidioc_qbuf			= solo_qbuf,
+	.vidioc_dqbuf			= solo_dqbuf,
+	.vidioc_streamon		= solo_streamon,
+        .vidioc_streamoff		= solo_streamoff,
+	/* Controls */
+	.vidioc_queryctrl		= solo_disp_queryctrl,
+        .vidioc_g_ctrl			= solo_disp_g_ctrl,
+        .vidioc_s_ctrl			= solo_disp_s_ctrl,
+};
+
+static struct video_device solo_v4l2_template = {
+	.name			= SOLO6010_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_M,
+	.current_norm		= V4L2_STD_NTSC_M,
+};
+
+int solo_v4l2_init(struct solo6010_dev *solo_dev)
+{
+	int ret;
+	int i;
+
+	init_waitqueue_head(&solo_dev->disp_thread_wait);
+
+	solo_dev->vfd = video_device_alloc();
+	if (!solo_dev->vfd)
+		return -ENOMEM;
+
+	*solo_dev->vfd = solo_v4l2_template;
+	solo_dev->vfd->parent = &solo_dev->pdev->dev;
+
+	ret = video_register_device(solo_dev->vfd, VFL_TYPE_GRABBER, video_nr);
+	if (ret < 0) {
+		video_device_release(solo_dev->vfd);
+		solo_dev->vfd = NULL;
+		return ret;
+	}
+
+	video_set_drvdata(solo_dev->vfd, solo_dev);
+
+	snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)",
+		 SOLO6010_NAME, solo_dev->vfd->num);
+
+	if (video_nr >= 0)
+		video_nr++;
+
+	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);
+
+	/* 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
+
+	solo6010_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN);
+
+	return 0;
+}
+
+void solo_v4l2_exit(struct solo6010_dev *solo_dev)
+{
+	solo6010_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN);
+	if (solo_dev->vfd) {
+		video_unregister_device(solo_dev->vfd);
+		solo_dev->vfd = NULL;
+	}
+}
diff --git a/drivers/staging/solo6x10/solo6010.h b/drivers/staging/solo6x10/solo6010.h
new file mode 100644
index 0000000..1c913d5
--- /dev/null
+++ b/drivers/staging/solo6x10/solo6010.h
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com
+ * Copyright (C) 2010 Ben Collins <bcollins at bluecherry.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SOLO6010_H
+#define __SOLO6010_H
+
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/i2c.h>
+#include <linux/semaphore.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <asm/io.h>
+#include <asm/atomic.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-dev.h>
+#include <media/videobuf-core.h>
+
+#include "solo6010-registers.h"
+
+#ifndef PCI_VENDOR_ID_SOFTLOGIC
+#define PCI_VENDOR_ID_SOFTLOGIC		0x9413
+#define PCI_DEVICE_ID_SOLO6010		0x6010
+#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
+/* Commell Softlogic 6010 based cards */
+#define PCI_DEVICE_ID_COMMSOLO_4	0x4E04
+#define PCI_DEVICE_ID_COMMSOLO_9	0x4E09
+#define PCI_DEVICE_ID_COMMSOLO_16	0x4E10
+#endif /* Bluecherry */
+
+#define SOLO6010_NAME			"solo6010"
+
+#define SOLO_MAX_CHANNELS		16
+
+/* Make sure these two match */
+#define SOLO6010_VERSION		"2.0.0"
+#define SOLO6010_VER_MAJOR		2
+#define SOLO6010_VER_MINOR		0
+#define SOLO6010_VER_SUB		0
+#define SOLO6010_VER_NUM \
+    KERNEL_VERSION(SOLO6010_VER_MAJOR, SOLO6010_VER_MINOR, SOLO6010_VER_SUB)
+
+/*
+ * The SOLO6010 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)
+/* MPEG and JPEG share the same interrupt and locks so they must be together
+ * in the same dma channel. */
+#define SOLO_P2M_DMA_ID_MP4E		0
+#define SOLO_P2M_DMA_ID_JPEG		0
+#define SOLO_P2M_DMA_ID_MP4D		1
+#define SOLO_P2M_DMA_ID_G723D		1
+#define SOLO_P2M_DMA_ID_DISP		2
+#define SOLO_P2M_DMA_ID_OSG		2
+#define SOLO_P2M_DMA_ID_G723E		3
+#define SOLO_P2M_DMA_ID_VIN		3
+
+/* Encoder standard modes */
+#define SOLO_ENC_MODE_CIF		2
+#define SOLO_ENC_MODE_HD1		1
+#define SOLO_ENC_MODE_D1		9
+
+#define SOLO_DEFAULT_GOP		30
+#define SOLO_DEFAULT_QP			3
+
+/* There is 8MB memory available for solo to buffer MPEG4 frames.
+ * This gives us 512 * 16kbyte queues. */
+#define SOLO_NR_RING_BUFS		512
+
+#define SOLO_CLOCK_MHZ			108
+
+#ifndef V4L2_BUF_FLAG_MOTION_ON
+#define V4L2_BUF_FLAG_MOTION_ON		0x0400
+#define V4L2_BUF_FLAG_MOTION_DETECTED	0x0800
+#endif
+#ifndef V4L2_CID_MOTION_ENABLE
+#define PRIVATE_CIDS
+#define V4L2_CID_MOTION_ENABLE		(V4L2_CID_PRIVATE_BASE+0)
+#define V4L2_CID_MOTION_THRESHOLD	(V4L2_CID_PRIVATE_BASE+1)
+#define V4L2_CID_MOTION_TRACE		(V4L2_CID_PRIVATE_BASE+2)
+#endif
+
+enum SOLO_I2C_STATE {
+	IIC_STATE_IDLE,
+	IIC_STATE_START,
+	IIC_STATE_READ,
+	IIC_STATE_WRITE,
+	IIC_STATE_STOP
+};
+
+struct solo_p2m_dev {
+	struct semaphore	sem;
+	struct completion	completion;
+	int			error;
+	u8			desc[SOLO_P2M_DESC_SIZE];
+};
+
+#define OSD_TEXT_MAX		30
+
+enum solo_enc_types {
+	SOLO_ENC_TYPE_STD,
+	SOLO_ENC_TYPE_EXT,
+};
+
+struct solo_enc_dev {
+	struct solo6010_dev	*solo_dev;
+	/* V4L2 Items */
+	struct video_device	*vfd;
+	/* General accounting */
+	wait_queue_head_t	thread_wait;
+	spinlock_t		lock;
+	atomic_t		readers;
+	u8			ch;
+	u8			mode, gop, qp, interlaced, interval;
+	u8			reset_gop;
+	u8			bw_weight;
+	u8			motion_detected;
+	u16			motion_thresh;
+	u16			width;
+	u16			height;
+	char			osd_text[OSD_TEXT_MAX + 1];
+};
+
+struct solo_enc_buf {
+	u8			vop;
+	u8			ch;
+	enum solo_enc_types	type;
+	u32			off;
+	u32			size;
+	u32			jpeg_off;
+	u32			jpeg_size;
+	struct timeval		ts;
+};
+
+/* The SOLO6010 PCI Device */
+struct solo6010_dev {
+	/* General stuff */
+	struct pci_dev		*pdev;
+	u8 __iomem		*reg_base;
+	int			nr_chans;
+	int			nr_ext;
+	u32			irq_mask;
+	u32			motion_mask;
+	spinlock_t		reg_io_lock;
+
+	/* tw28xx accounting */
+	u8			tw2864, tw2815;
+	u8			tw28_cnt;
+
+	/* i2c related items */
+	struct i2c_adapter	i2c_adap[SOLO_I2C_ADAPTERS];
+	enum SOLO_I2C_STATE	i2c_state;
+	struct semaphore	i2c_sem;
+	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];
+
+	/* 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;
+
+	/* V4L2 Encoder items */
+	struct solo_enc_dev	*v4l2_enc[SOLO_MAX_CHANNELS];
+	u16			enc_bw_remain;
+	/* IDX into hw mp4 encoder */
+	u8			enc_idx;
+	/* Our software ring of enc buf references */
+	u16			enc_wr_idx;
+	struct solo_enc_buf	enc_buf[SOLO_NR_RING_BUFS];
+
+	/* Current video settings */
+	u32 			video_type;
+	u16			video_hsize, video_vsize;
+	u16			vout_hstart, vout_vstart;
+	u16			vin_hstart, vin_vstart;
+	u8			fps;
+
+	/* Audio components */
+	struct snd_card		*snd_card;
+	struct snd_pcm		*snd_pcm;
+	atomic_t		snd_users;
+	int			g723_hw_idx;
+};
+
+static inline u32 solo_reg_read(struct solo6010_dev *solo_dev, int reg)
+{
+	unsigned long flags;
+	u32 ret;
+	u16 val;
+
+	spin_lock_irqsave(&solo_dev->reg_io_lock, flags);
+
+	ret = readl(solo_dev->reg_base + reg);
+	rmb();
+	pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val);
+	rmb();
+
+	spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags);
+
+	return ret;
+}
+
+static inline void solo_reg_write(struct solo6010_dev *solo_dev, int reg,
+				      u32 data)
+{
+	unsigned long flags;
+	u16 val;
+
+	spin_lock_irqsave(&solo_dev->reg_io_lock, flags);
+
+	writel(data, solo_dev->reg_base + reg);
+	wmb();
+	pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val);
+	rmb();
+
+	spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags);
+}
+
+void solo6010_irq_on(struct solo6010_dev *solo_dev, u32 mask);
+void solo6010_irq_off(struct solo6010_dev *solo_dev, u32 mask);
+
+/* Init/exit routeines for subsystems */
+int solo_disp_init(struct solo6010_dev *solo_dev);
+void solo_disp_exit(struct solo6010_dev *solo_dev);
+
+int solo_gpio_init(struct solo6010_dev *solo_dev);
+void solo_gpio_exit(struct solo6010_dev *solo_dev);
+
+int solo_i2c_init(struct solo6010_dev *solo_dev);
+void solo_i2c_exit(struct solo6010_dev *solo_dev);
+
+int solo_p2m_init(struct solo6010_dev *solo_dev);
+void solo_p2m_exit(struct solo6010_dev *solo_dev);
+
+int solo_v4l2_init(struct solo6010_dev *solo_dev);
+void solo_v4l2_exit(struct solo6010_dev *solo_dev);
+
+int solo_enc_init(struct solo6010_dev *solo_dev);
+void solo_enc_exit(struct solo6010_dev *solo_dev);
+
+int solo_enc_v4l2_init(struct solo6010_dev *solo_dev);
+void solo_enc_v4l2_exit(struct solo6010_dev *solo_dev);
+
+int solo_g723_init(struct solo6010_dev *solo_dev);
+void solo_g723_exit(struct solo6010_dev *solo_dev);
+
+/* ISR's */
+int solo_i2c_isr(struct solo6010_dev *solo_dev);
+void solo_p2m_isr(struct solo6010_dev *solo_dev, int id);
+void solo_p2m_error_isr(struct solo6010_dev *solo_dev, u32 status);
+void solo_enc_v4l2_isr(struct solo6010_dev *solo_dev);
+void solo_g723_isr(struct solo6010_dev *solo_dev);
+void solo_motion_isr(struct solo6010_dev *solo_dev);
+void solo_video_in_isr(struct solo6010_dev *solo_dev);
+
+/* i2c read/write */
+u8 solo_i2c_readbyte(struct solo6010_dev *solo_dev, int id, u8 addr, u8 off);
+void solo_i2c_writebyte(struct solo6010_dev *solo_dev, int id, u8 addr, u8 off,
+			u8 data);
+
+/* P2M DMA */
+int solo_p2m_dma_t(struct solo6010_dev *solo_dev, u8 id, int wr,
+		   dma_addr_t dma_addr, u32 ext_addr, u32 size);
+int solo_p2m_dma(struct solo6010_dev *solo_dev, u8 id, int wr,
+		 void *sys_addr, u32 ext_addr, u32 size);
+
+/* Set the threshold for motion detection */
+void solo_set_motion_threshold(struct solo6010_dev *solo_dev, u8 ch, u16 val);
+#define SOLO_DEF_MOT_THRESH		0x0300
+
+/* Write text on OSD */
+int solo_osd_print(struct solo_enc_dev *solo_enc);
+
+#endif /* __SOLO6010_H */
-- 
1.7.1




More information about the devel mailing list